| author | |
| committer | |
| log | 878b4a52ab2bcb1aa6ef93be7f006594429deb4a |
| tree | f3b529755e49a5528ac507310a8616de821dc1c4 |
| parent | 05027137f9f30b0179a4f73df88c69aaf898d2de |
| signature | Signed by SSH key SHA256:52mNGHRsVFBDED9IAX5pe+LRWUefqTbxEReunq21QvU |
Edits enter onestore as ops through Section::apply and leave as one appended
revision: a keystroke on a 3000-paragraph page lowers, applies and seals in
about 35 µs and appends ~0.8 KB. Publishing compares the 1 KiB header and length
instead of the whole file, idle polls read only the header (SMB gains a stamp),
and nearby patches merge into one write. Ops match the page writer across 33
edit families and cold-open in OneNote 2010. The writer also fixes splits in
lists, emits OneNote's indent table and page margins, rewrites emptied inline
math, and sends two split/reformat merges to review as OneNote does.
Assisted-by: claude-opus-5.5
Assisted-by: claude-opus-5330 files changed, 15199 insertions(+), 4527 deletions(-)
corpus/link-edit/internal/candidate/Open Notebook.onetoc2| Binary files a/corpus/link-edit/internal/candidate/Open Notebook.onetoc2 and b/corpus/link-edit/internal/candidate/Open Notebook.onetoc2 differ | |||
corpus/link-edit/internal/candidate/links.one| Binary files a/corpus/link-edit/internal/candidate/links.one and b/corpus/link-edit/internal/candidate/links.one differ | |||
corpus/link-edit/internal/cold/commands.jsonl+1-1| ... | @@ -1,3 +1,3 @@ | ... | @@ -1,3 +1,3 @@ |
| 1 | {"command": "powershell -NoProfile -Command \"Expand-Archive -LiteralPath C:\\one-tests\\transfer.zip -DestinationPath C:\\one-tests\\runs\\capture\\notebook\"", "exit": 0, "stdout": "", "stderr": "", "error": null} | 1 | {"command": "powershell -NoProfile -Command \"Expand-Archive -LiteralPath C:\\one-tests\\transfer.zip -DestinationPath C:\\one-tests\\runs\\capture\\notebook\"", "exit": 0, "stdout": "", "stderr": "", "error": null} |
| 2 | {"command": "powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File C:\\one-tests\\read-current.ps1 -Root C:\\one-tests\\runs\\capture -CloneHost ONE-M6-A4418B81 -ExpectedPages 2", "exit": 0, "stdout": "Read 1 sections and 2 pages.\r\n", "stderr": "", "error": null} | 2 | {"command": "powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File C:\\one-tests\\read-current.ps1 -Root C:\\one-tests\\runs\\capture -CloneHost ONE-M6-AFCAA40A -ExpectedPages 2", "exit": 0, "stdout": "Read 1 sections and 2 pages.\r\n", "stderr": "", "error": null} |
| 3 | {"command": "powershell -NoProfile -Command \"Compress-Archive -Force -Path C:\\one-tests\\runs\\capture\\* -DestinationPath C:\\one-tests\\captured.zip\"", "exit": 0, "stdout": "", "stderr": "", "error": null} | 3 | {"command": "powershell -NoProfile -Command \"Compress-Archive -Force -Path C:\\one-tests\\runs\\capture\\* -DestinationPath C:\\one-tests\\captured.zip\"", "exit": 0, "stdout": "", "stderr": "", "error": null} |
corpus/link-edit/internal/cold/machine.json+1-1| ... | @@ -1 +1 @@ | ... | @@ -1 +1 @@ |
| 1 | {"name": "m6-a4418b81", "hostname": "ONE-M6-A4418B81"} | 1 | {"name": "m6-afcaa40a", "hostname": "ONE-M6-AFCAA40A"} |
corpus/link-edit/internal/cold/notebook/Open Notebook.onetoc2| Binary files a/corpus/link-edit/internal/cold/notebook/Open Notebook.onetoc2 and b/corpus/link-edit/internal/cold/notebook/Open Notebook.onetoc2 differ | |||
corpus/link-edit/internal/cold/notebook/links.one| Binary files a/corpus/link-edit/internal/cold/notebook/links.one and b/corpus/link-edit/internal/cold/notebook/links.one differ | |||
corpus/link-edit/internal/cold/read/environment.json+1-1| ... | @@ -1,7 +1,7 @@ | ... | @@ -1,7 +1,7 @@ |
| 1 | { | 1 | { |
| 2 | "powershell": "5.1.14409.1005", | 2 | "powershell": "5.1.14409.1005", |
| 3 | "schema": "xs2010", | 3 | "schema": "xs2010", |
| 4 | "hostname": "ONE-M6-A4418B81", | 4 | "hostname": "ONE-M6-AFCAA40A", |
| 5 | "cold": true, | 5 | "cold": true, |
| 6 | "onenote": "14.0.4763.1000" | 6 | "onenote": "14.0.4763.1000" |
| 7 | } | 7 | } |
corpus/link-edit/internal/cold/read/hierarchy.xml+1-1| ... | @@ -1,2 +1,2 @@ | ... | @@ -1,2 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Notebook xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" name="notebook" nickname="notebook" ID="{A475638B-F081-40E1-B0A7-056122CE68EA}{1}{B0}" path="C:\one-tests\runs\capture\notebook" lastModifiedTime="2026-09-12T22:13:46.000Z" color="#B49EDE"><one:Section name="links" ID="{D78A6D3C-7091-0DF6-2EFC-514D3F2E4C85}{1}{B0}" path="C:\one-tests\runs\capture\notebook\links.one" lastModifiedTime="2026-09-12T22:13:46.000Z" color="#8AA8E4"><one:Page ID="{8D0B3F30-F486-0365-1D6A-714CBE91E07B}{1}{B0}" name="Linking page HYPERLINK &quot;onenote:#Link%20target&amp;section-id={A45E149D-BEE4-4BB2-AF69-072EF644999A}&amp;page-id={1E1AB5A6-E512-42BE-A763-78914DE104F9}&amp;end&amp;base-path=C:\one-tests\runs\capture\notebook\links.one&quot;Link target" dateTime="2026-09-12T22:12:09.000Z" lastModifiedTime="2026-09-12T22:13:46.000Z" pageLevel="1"/><one:Page ID="{109B4059-8CAB-09BB-0559-34EA53917DD2}{1}{B0}" name="Link target" dateTime="2026-09-12T22:12:09.000Z" lastModifiedTime="2026-09-12T22:13:46.000Z" pageLevel="1"/></one:Section></one:Notebook> | 2 | <one:Notebook xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" name="notebook" nickname="notebook" ID="{B6B691A5-ABB0-4D79-A8B4-E7B3BF418EA1}{1}{B0}" path="C:\one-tests\runs\capture\notebook" lastModifiedTime="2026-09-26T06:43:14.000Z" color="#B49EDE"><one:Section name="links" ID="{ADD0F0B5-86CF-08D7-1232-F70E1DB09222}{1}{B0}" path="C:\one-tests\runs\capture\notebook\links.one" lastModifiedTime="2026-09-26T06:43:14.000Z" color="#8AA8E4"><one:Page ID="{6DBDA204-8CAD-0128-3EA6-FC8763E619DC}{1}{B0}" name="Linking page HYPERLINK &quot;onenote:#Link%20target&amp;section-id={912D18F5-8E97-4C22-B11D-AA61E6E7D4B1}&amp;page-id={54104D3B-F876-47C6-8009-D90D3DEC5217}&amp;end&amp;base-path=C:\one-tests\runs\capture\notebook\links.one&quot;Link target" dateTime="2026-09-26T06:32:48.000Z" lastModifiedTime="2026-09-26T06:43:14.000Z" pageLevel="1"/><one:Page ID="{6EB3ECAC-4EB9-0237-0924-FBB7F1FFE5E5}{1}{B0}" name="Link target" dateTime="2026-09-26T06:32:48.000Z" lastModifiedTime="2026-09-26T06:43:14.000Z" pageLevel="1"/></one:Section></one:Notebook> |
corpus/link-edit/internal/cold/read/page-000.xml+2-1| ... | @@ -1,2 +1,3 @@ | ... | @@ -1,2 +1,3 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{109B4059-8CAB-09BB-0559-34EA53917DD2}{1}{B0}" name="Link target" dateTime="2026-09-12T22:12:09.000Z" lastModifiedTime="2026-09-12T22:13:46.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-12T22:12:09.000Z" lastModifiedTime="2026-09-12T22:12:09.000Z" objectID="{109B4059-8CAB-09BB-0559-34EA53917DD2}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Link target]]></one:T></one:OE></one:Title></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{6DBDA204-8CAD-0128-3EA6-FC8763E619DC}{1}{B0}" name="Linking page HYPERLINK &quot;onenote:#Link%20target&amp;section-id={912D18F5-8E97-4C22-B11D-AA61E6E7D4B1}&amp;page-id={54104D3B-F876-47C6-8009-D90D3DEC5217}&amp;end&amp;base-path=C:\one-tests\runs\capture\notebook\links.one&quot;Link target" dateTime="2026-09-26T06:32:48.000Z" lastModifiedTime="2026-09-26T06:43:14.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-26T06:32:48.000Z" objectID="{ADD0F0B5-86CF-08D7-1232-F70E1DB09222}{23}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="172.3725128173828" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-26T06:32:48.000Z" lastModifiedTime="2026-09-26T06:32:48.000Z" objectID="{ADD0F0B5-86CF-08D7-1232-F70E1DB09222}{24}{B0}" alignment="left"><one:T><![CDATA[Linking page <a |
| 3 | href="onenote:#Link%20target&amp;section-id={912D18F5-8E97-4C22-B11D-AA61E6E7D4B1}&amp;page-id={54104D3B-F876-47C6-8009-D90D3DEC5217}&amp;end&amp;base-path=C:\one-tests\runs\capture\notebook\links.one">Link target</a>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/link-edit/internal/cold/read/page-001.xml+1-2| ... | @@ -1,3 +1,2 @@ | ... | @@ -1,3 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{8D0B3F30-F486-0365-1D6A-714CBE91E07B}{1}{B0}" name="Linking page HYPERLINK &quot;onenote:#Link%20target&amp;section-id={A45E149D-BEE4-4BB2-AF69-072EF644999A}&amp;page-id={1E1AB5A6-E512-42BE-A763-78914DE104F9}&amp;end&amp;base-path=C:\one-tests\runs\capture\notebook\links.one&quot;Link target" dateTime="2026-09-12T22:12:09.000Z" lastModifiedTime="2026-09-12T22:13:46.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-12T22:12:09.000Z" objectID="{D78A6D3C-7091-0DF6-2EFC-514D3F2E4C85}{23}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="172.3725128173828" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-12T22:12:09.000Z" lastModifiedTime="2026-09-12T22:12:09.000Z" objectID="{D78A6D3C-7091-0DF6-2EFC-514D3F2E4C85}{24}{B0}" alignment="left"><one:T><![CDATA[Linking page <a | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{6EB3ECAC-4EB9-0237-0924-FBB7F1FFE5E5}{1}{B0}" name="Link target" dateTime="2026-09-26T06:32:48.000Z" lastModifiedTime="2026-09-26T06:43:14.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-26T06:32:48.000Z" lastModifiedTime="2026-09-26T06:32:48.000Z" objectID="{6EB3ECAC-4EB9-0237-0924-FBB7F1FFE5E5}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Link target]]></one:T></one:OE></one:Title></one:Page> |
| 3 | href="onenote:#Link%20target&amp;section-id={A45E149D-BEE4-4BB2-AF69-072EF644999A}&amp;page-id={1E1AB5A6-E512-42BE-A763-78914DE104F9}&amp;end&amp;base-path=C:\one-tests\runs\capture\notebook\links.one">Link target</a>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/link-edit/internal/cold/run.json+1-1| ... | @@ -1,5 +1,5 @@ | ... | @@ -1,5 +1,5 @@ |
| 1 | { | 1 | { |
| 2 | "notebook": "/private/tmp/m4/ilink", | 2 | "notebook": "/Users/clo/dev/one/evidence/titles/link-internal", |
| 3 | "expected_pages": 2, | 3 | "expected_pages": 2, |
| 4 | "author": null, | 4 | "author": null, |
| 5 | "author_timeout_seconds": 600, | 5 | "author_timeout_seconds": 600, |
corpus/link-edit/internal/cold/source.json+5-5| ... | @@ -2,13 +2,13 @@ | ... | @@ -2,13 +2,13 @@ |
| 2 | { | 2 | { |
| 3 | "path": "Open Notebook.onetoc2", | 3 | "path": "Open Notebook.onetoc2", |
| 4 | "bytes": 1604, | 4 | "bytes": 1604, |
| 5 | "sha256": "fdee47831417ef5d21624890fe8d9f3d73a4ded56cacbbe2ebb978788786681b", | 5 | "sha256": "7cf4bd0ba068eaccb8e2d3306472ac133a08966eb9dc42cc2b4f7c3b0f44b03f", |
| 6 | "mtime_ns": 1789251129798178239 | 6 | "mtime_ns": 1790404368862297325 |
| 7 | }, | 7 | }, |
| 8 | { | 8 | { |
| 9 | "path": "links.one", | 9 | "path": "links.one", |
| 10 | "bytes": 9200, | 10 | "bytes": 10120, |
| 11 | "sha256": "16b69f8178ce8a23e37ca844b89ae736f4e2c57827205fcfc1e51501b2865d17", | 11 | "sha256": "740b61f789cd951855f67a811394b77ed1cbcb500e728d4433b1e71904ab1986", |
| 12 | "mtime_ns": 1789251129797893821 | 12 | "mtime_ns": 1790404368862105283 |
| 13 | } | 13 | } |
| 14 | ] | 14 | ] |
corpus/page-copy/candidate/Open Notebook.onetoc2| Binary files a/corpus/page-copy/candidate/Open Notebook.onetoc2 and b/corpus/page-copy/candidate/Open Notebook.onetoc2 differ | |||
corpus/page-copy/candidate/copies.one| Binary files a/corpus/page-copy/candidate/copies.one and b/corpus/page-copy/candidate/copies.one differ | |||
corpus/page-copy/cold/commands.jsonl+1-1| ... | @@ -1,3 +1,3 @@ | ... | @@ -1,3 +1,3 @@ |
| 1 | {"command": "powershell -NoProfile -Command \"Expand-Archive -LiteralPath C:\\one-tests\\transfer.zip -DestinationPath C:\\one-tests\\runs\\capture\\notebook\"", "exit": 0, "stdout": "", "stderr": "", "error": null} | 1 | {"command": "powershell -NoProfile -Command \"Expand-Archive -LiteralPath C:\\one-tests\\transfer.zip -DestinationPath C:\\one-tests\\runs\\capture\\notebook\"", "exit": 0, "stdout": "", "stderr": "", "error": null} |
| 2 | {"command": "powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File C:\\one-tests\\read-current.ps1 -Root C:\\one-tests\\runs\\capture -CloneHost ONE-M6-33218F72 -ExpectedPages 17 -KeepOpen", "exit": 0, "stdout": "Read 1 sections and 17 pages.\r\n", "stderr": "", "error": null} | 2 | {"command": "powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File C:\\one-tests\\read-current.ps1 -Root C:\\one-tests\\runs\\capture -CloneHost ONE-M6-DD3BC9D2 -ExpectedPages 17 -KeepOpen", "exit": 0, "stdout": "Read 1 sections and 17 pages.\r\n", "stderr": "", "error": null} |
| 3 | {"command": "powershell -NoProfile -Command \"Compress-Archive -Force -Path C:\\one-tests\\runs\\capture\\* -DestinationPath C:\\one-tests\\captured.zip\"", "exit": 0, "stdout": "", "stderr": "", "error": null} | 3 | {"command": "powershell -NoProfile -Command \"Compress-Archive -Force -Path C:\\one-tests\\runs\\capture\\* -DestinationPath C:\\one-tests\\captured.zip\"", "exit": 0, "stdout": "", "stderr": "", "error": null} |
corpus/page-copy/cold/machine.json+1-1| ... | @@ -1 +1 @@ | ... | @@ -1 +1 @@ |
| 1 | {"name": "m6-33218f72", "hostname": "ONE-M6-33218F72"} | 1 | {"name": "m6-dd3bc9d2", "hostname": "ONE-M6-DD3BC9D2"} |
corpus/page-copy/cold/notebook/Open Notebook.onetoc2| Binary files a/corpus/page-copy/cold/notebook/Open Notebook.onetoc2 and b/corpus/page-copy/cold/notebook/Open Notebook.onetoc2 differ | |||
corpus/page-copy/cold/notebook/copies.one| Binary files a/corpus/page-copy/cold/notebook/copies.one and b/corpus/page-copy/cold/notebook/copies.one differ | |||
corpus/page-copy/cold/read/environment.json+1-1| ... | @@ -1,7 +1,7 @@ | ... | @@ -1,7 +1,7 @@ |
| 1 | { | 1 | { |
| 2 | "powershell": "5.1.14409.1005", | 2 | "powershell": "5.1.14409.1005", |
| 3 | "schema": "xs2010", | 3 | "schema": "xs2010", |
| 4 | "hostname": "ONE-M6-33218F72", | 4 | "hostname": "ONE-M6-DD3BC9D2", |
| 5 | "cold": true, | 5 | "cold": true, |
| 6 | "onenote": "14.0.4763.1000" | 6 | "onenote": "14.0.4763.1000" |
| 7 | } | 7 | } |
corpus/page-copy/cold/read/hierarchy.xml+1-1| ... | @@ -1,2 +1,2 @@ | ... | @@ -1,2 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Notebook xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" name="notebook" nickname="notebook" ID="{3D7C62EC-5CB3-485B-BB1F-23FA56C52E8D}{1}{B0}" path="C:\one-tests\runs\capture\notebook" lastModifiedTime="2026-09-13T08:14:37.000Z" color="#B49EDE"><one:Section name="copies" ID="{8631621B-321A-0F71-03FE-3BCA53E86583}{1}{B0}" path="C:\one-tests\runs\capture\notebook\copies.one" lastModifiedTime="2026-09-13T08:14:37.000Z" color="#8AA8E4"><one:Page ID="{0A5A2909-7069-0DA0-2457-FE8F9CA25885}{1}{B0}" name="Destination" dateTime="2026-09-13T08:12:55.000Z" lastModifiedTime="2026-09-13T08:14:26.000Z" pageLevel="1"/><one:Page ID="{62E12C49-D5FE-0850-17D5-B4DC95B736E9}{1}{B0}" name="Before the file" dateTime="2026-09-13T08:12:55.000Z" lastModifiedTime="2026-09-13T08:14:26.000Z" pageLevel="1"/><one:Page ID="{FBBA4589-4114-0DBB-1DDE-9AEBE04435E8}{1}{B0}" name="Before the table" dateTime="2026-09-13T08:12:55.000Z" lastModifiedTime="2026-09-13T08:14:26.000Z" pageLevel="1"/><one:Page ID="{5E04C302-1E83-0CCD-1715-B0A2566E0191}{1}{B0}" name="Bold 🦀 italic é color 東京" dateTime="2026-09-13T08:12:55.000Z" lastModifiedTime="2026-09-13T08:14:26.000Z" pageLevel="1"/><one:Page ID="{55B596AA-4540-085D-142D-9F788431ACE5}{1}{B0}" name="Indent first subtree" dateTime="2026-09-13T08:12:55.000Z" lastModifiedTime="2026-09-13T08:14:27.000Z" pageLevel="1"/><one:Page ID="{BA810F75-DBC0-0827-0990-F9FBFC84B7D7}{1}{B0}" name="Delete cell subtree" dateTime="2026-09-13T08:12:56.000Z" lastModifiedTime="2026-09-13T08:14:27.000Z" pageLevel="1"/><one:Page ID="{493E9D0C-5976-0886-2ADB-636A30B81D20}{1}{B0}" name="Delete sole cell paragraph" dateTime="2026-09-13T08:12:56.000Z" lastModifiedTime="2026-09-13T08:14:27.000Z" pageLevel="1"/><one:Page ID="{79EFBB50-C028-09DE-005B-75E21D0ED54C}{1}{B0}" name="Move numbered subtree down" dateTime="2026-09-13T08:12:56.000Z" lastModifiedTime="2026-09-13T08:14:27.000Z" pageLevel="1"/><one:Page ID="{6EC9E652-85BD-0524-17E7-232F5951B731}{1}{B0}" name="Delete numbered subtree" dateTime="2026-09-13T08:12:57.000Z" lastModifiedTime="2026-09-13T08:14:27.000Z" pageLevel="1"/><one:Page ID="{25E90FFA-6C7E-0A8D-3F8B-FC658F7AAF3B}{1}{B0}" name="Indent bullet subtree" dateTime="2026-09-13T08:12:57.000Z" lastModifiedTime="2026-09-13T08:14:27.000Z" pageLevel="1"/><one:Page ID="{AA29BE5C-9249-0610-1036-E2735CEC0BB9}{1}{B0}" name="Move cell subtree down" dateTime="2026-09-13T08:12:58.000Z" lastModifiedTime="2026-09-13T08:14:27.000Z" pageLevel="1"/><one:Page ID="{1E9DFA9B-2BEB-0C4E-3FEA-12F1C294A76E}{1}{B0}" name="Before the picture" dateTime="2026-09-13T08:12:58.000Z" lastModifiedTime="2026-09-13T08:14:27.000Z" pageLevel="1"/><one:Page ID="{D28E7576-C55F-0047-2C51-4C855C536D13}{1}{B0}" name="Beside the drawing" dateTime="2026-09-13T08:12:58.000Z" lastModifiedTime="2026-09-13T08:14:27.000Z" pageLevel="1"/><one:Page ID="{8E5634C2-26CB-0458-2810-125D129037C8}{1}{B0}" name="Open task" dateTime="2026-09-13T08:12:59.000Z" lastModifiedTime="2026-09-13T08:14:27.000Z" pageLevel="1"/><one:Page ID="{35E45165-B253-07BF-1324-6FC7DCE3552B}{1}{B0}" name="Bullet item" dateTime="2026-09-13T08:12:59.000Z" lastModifiedTime="2026-09-13T08:14:27.000Z" pageLevel="1"/><one:Page ID="{78595C91-C5E1-0D03-3A98-A98CA31DE5A8}{1}{B0}" name="Equations below" dateTime="2026-09-13T08:12:59.000Z" lastModifiedTime="2026-09-13T08:14:27.000Z" pageLevel="1"/><one:Page ID="{EF547C09-87EA-0A35-039B-F5512549E931}{1}{B0}" name="A notebook created in Rust." dateTime="2026-09-13T08:13:00.000Z" lastModifiedTime="2026-09-13T08:14:27.000Z" pageLevel="1"/></one:Section></one:Notebook> | 2 | <one:Notebook xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" name="notebook" nickname="notebook" ID="{296735F4-6C81-4634-82FA-64EAA51CBFAE}{1}{B0}" path="C:\one-tests\runs\capture\notebook" lastModifiedTime="2026-09-26T06:51:08.000Z" color="#B49EDE"><one:Section name="copies" ID="{D9F74361-435D-0594-006D-92ECD10C11D3}{1}{B0}" path="C:\one-tests\runs\capture\notebook\copies.one" lastModifiedTime="2026-09-26T06:51:08.000Z" color="#8AA8E4"><one:Page ID="{64D50BBC-095D-0B40-049D-EE63CB34BC4D}{1}{B0}" name="Destination" dateTime="2026-09-26T06:33:47.000Z" lastModifiedTime="2026-09-26T06:50:55.000Z" pageLevel="1"/><one:Page ID="{0C0ECE53-81B9-0DE0-2721-D119E26F7632}{1}{B0}" name="Before the file" dateTime="2026-09-26T06:33:47.000Z" lastModifiedTime="2026-09-26T06:50:55.000Z" pageLevel="1"/><one:Page ID="{8BD579FF-C812-0827-2B91-968E287CBC55}{1}{B0}" name="Before the table" dateTime="2026-09-26T06:33:47.000Z" lastModifiedTime="2026-09-26T06:50:55.000Z" pageLevel="1"/><one:Page ID="{E0635CAD-AE50-05FE-22E9-82F2F75DB84D}{1}{B0}" name="Bold 🦀 italic é color 東京" dateTime="2026-09-26T06:33:47.000Z" lastModifiedTime="2026-09-26T06:50:55.000Z" pageLevel="1"/><one:Page ID="{BFC636CB-B7BE-022A-1F7C-066F34B2A754}{1}{B0}" name="Indent first subtree" dateTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:50:55.000Z" pageLevel="1"/><one:Page ID="{885E9DE1-BDB8-02C4-1534-B3C832F89C02}{1}{B0}" name="Delete cell subtree" dateTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:50:55.000Z" pageLevel="1"/><one:Page ID="{1EBCDDF8-1393-04D1-0266-929499FE03EC}{1}{B0}" name="Delete sole cell paragraph" dateTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:50:56.000Z" pageLevel="1"/><one:Page ID="{25A63247-8FFB-0C98-22D9-2B99C3661F5A}{1}{B0}" name="Move numbered subtree down" dateTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:50:56.000Z" pageLevel="1"/><one:Page ID="{92FCCBE6-488E-0D05-1C9E-91FA438C6A79}{1}{B0}" name="Delete numbered subtree" dateTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:50:56.000Z" pageLevel="1"/><one:Page ID="{364CF1D3-423A-0B9F-1019-BE369236958F}{1}{B0}" name="Indent bullet subtree" dateTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:50:56.000Z" pageLevel="1"/><one:Page ID="{6E15C33B-F88D-0966-239D-1EB519063D70}{1}{B0}" name="Move cell subtree down" dateTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:50:56.000Z" pageLevel="1"/><one:Page ID="{918C50B7-7848-0240-056C-816588770AB5}{1}{B0}" name="Before the picture" dateTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:50:56.000Z" pageLevel="1"/><one:Page ID="{CCD62F34-CB06-0E93-2181-938C6C097557}{1}{B0}" name="Beside the drawing" dateTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:50:56.000Z" pageLevel="1"/><one:Page ID="{DE01F667-2BDD-03D0-14A4-27E9A5F8CC0C}{1}{B0}" name="Open task" dateTime="2026-09-26T06:33:49.000Z" lastModifiedTime="2026-09-26T06:50:56.000Z" pageLevel="1"/><one:Page ID="{9A577992-FE92-07C3-0C41-250E4E607861}{1}{B0}" name="Bullet item" dateTime="2026-09-26T06:33:49.000Z" lastModifiedTime="2026-09-26T06:51:08.000Z" pageLevel="1"/><one:Page ID="{2C4189F4-1EE4-047C-160D-979FD528FBCF}{1}{B0}" name="Equations below" dateTime="2026-09-26T06:33:49.000Z" lastModifiedTime="2026-09-26T06:50:56.000Z" pageLevel="1"/><one:Page ID="{CF7197EA-123A-02D7-12CE-E9F5A3788534}{1}{B0}" name="A notebook created in Rust." dateTime="2026-09-26T06:33:49.000Z" lastModifiedTime="2026-09-26T06:50:56.000Z" pageLevel="1"/></one:Section></one:Notebook> |
corpus/page-copy/cold/read/page-000.navigation.json+1-1| ... | @@ -6,7 +6,7 @@ | ... | @@ -6,7 +6,7 @@ |
| 6 | "h": 600, | 6 | "h": 600, |
| 7 | "error": null, | 7 | "error": null, |
| 8 | "win": { | 8 | "win": { |
| 9 | "title": "Destination - Microsoft OneNote", | 9 | "title": "Before the file - Microsoft OneNote", |
| 10 | "class": "Framework::CFrame", | 10 | "class": "Framework::CFrame", |
| 11 | "dialog": false | 11 | "dialog": false |
| 12 | } | 12 | } |
corpus/page-copy/cold/read/page-000.png| Binary files a/corpus/page-copy/cold/read/page-000.png and b/corpus/page-copy/cold/read/page-000.png differ | |||
corpus/page-copy/cold/read/page-000.xml+1-1| ... | @@ -1,2 +1,2 @@ | ... | @@ -1,2 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{0A5A2909-7069-0DA0-2457-FE8F9CA25885}{1}{B0}" name="Destination" dateTime="2026-09-13T08:12:55.000Z" lastModifiedTime="2026-09-13T08:14:26.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-13T08:12:55.000Z" objectID="{8631621B-321A-0F71-03FE-3BCA53E86583}{23}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="118.3393707275391" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-13T08:12:55.000Z" lastModifiedTime="2026-09-13T08:12:55.000Z" objectID="{8631621B-321A-0F71-03FE-3BCA53E86583}{24}{B0}" alignment="left"><one:T><![CDATA[Destination]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{0C0ECE53-81B9-0DE0-2721-D119E26F7632}{1}{B0}" name="Before the file" dateTime="2026-09-26T06:33:47.000Z" lastModifiedTime="2026-09-26T06:50:55.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Copier" lastModifiedBy="Copier" creationTime="2026-09-26T06:33:47.000Z" lastModifiedTime="2026-09-26T06:33:47.000Z" objectID="{0C0ECE53-81B9-0DE0-2721-D119E26F7632}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Before the file]]></one:T></one:OE></one:Title><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-26T06:33:47.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{8}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="130.4619598388672" height="89.85542297363281"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-26T06:33:47.000Z" lastModifiedTime="2026-09-26T06:33:47.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{10}{B0}" alignment="left" style="font-family:Calibri;font-size:11.0pt"><one:T><![CDATA[Before the file]]></one:T></one:OE><one:OE creationTime="2026-09-26T06:33:47.000Z" lastModifiedTime="2026-09-26T06:33:47.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{12}{B0}" alignment="left"><one:InsertedFile pathCache="C:\Users\clover\AppData\Local\Microsoft\OneNote\14.0\OneNoteOfflineCache_Files\ac39c6fd-e843-438f-8c2a-61db83402513.txt" preferredName="notes 🦀.txt"/></one:OE><one:OE creationTime="2026-09-26T06:33:47.000Z" lastModifiedTime="2026-09-26T06:33:47.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{14}{B0}" alignment="left" style="font-family:Calibri;font-size:11.0pt"><one:T><![CDATA[After the file]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/page-copy/cold/read/page-001.navigation.json+1-1| ... | @@ -6,7 +6,7 @@ | ... | @@ -6,7 +6,7 @@ |
| 6 | "h": 600, | 6 | "h": 600, |
| 7 | "error": null, | 7 | "error": null, |
| 8 | "win": { | 8 | "win": { |
| 9 | "title": "Before the picture - Microsoft OneNote", | 9 | "title": "Delete sole cell paragraph - Microsoft OneNote", |
| 10 | "class": "Framework::CFrame", | 10 | "class": "Framework::CFrame", |
| 11 | "dialog": false | 11 | "dialog": false |
| 12 | } | 12 | } |
corpus/page-copy/cold/read/page-001.png| Binary files a/corpus/page-copy/cold/read/page-001.png and b/corpus/page-copy/cold/read/page-001.png differ | |||
corpus/page-copy/cold/read/page-001.xml+3-3| ... | @@ -1,4 +1,4 @@ | ... | @@ -1,4 +1,4 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{1E9DFA9B-2BEB-0C4E-3FEA-12F1C294A76E}{1}{B0}" name="Before the picture" dateTime="2026-09-13T08:12:58.000Z" lastModifiedTime="2026-09-13T08:14:27.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Copier" lastModifiedBy="Copier" creationTime="2026-09-13T08:12:58.000Z" lastModifiedTime="2026-09-13T08:12:58.000Z" objectID="{1E9DFA9B-2BEB-0C4E-3FEA-12F1C294A76E}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Before the picture]]></one:T></one:OE></one:Title><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-13T08:12:59.000Z" objectID="{4DA80AD4-95E4-0563-13A1-48595B5FF4B8}{1}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="148.3100738525391" height="27.60543251037598"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-13T08:12:59.000Z" lastModifiedTime="2026-09-13T08:12:59.000Z" objectID="{AFD96465-6096-07A5-2297-9F5092B2C4D0}{1}{B0}" alignment="left" style="font-family:Calibri;font-size:11.0pt"><one:T><![CDATA[Before the picture]]></one:T></one:OE><one:OE creationTime="2026-09-13T08:12:59.000Z" lastModifiedTime="2026-09-13T08:12:59.000Z" objectID="{EAFF9A35-4964-0B98-232B-BE4FC1E5AC7A}{1}{B0}" alignment="left"><one:Image format="png"><one:Data>iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVQI12P4z8AAAAMBAQAY3Y2w | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{1EBCDDF8-1393-04D1-0266-929499FE03EC}{1}{B0}" name="Delete sole cell paragraph" dateTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:50:56.000Z" pageLevel="1" lang="en-US"><one:TagDef index="0" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Preserved task"/><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:QuickStyleDef index="1" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Copier" lastModifiedBy="Copier" creationTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{1EBCDDF8-1393-04D1-0266-929499FE03EC}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Delete sole cell paragraph]]></one:T></one:OE></one:Title><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{5778544D-D680-0FAA-328B-B80D7F87C704}{40}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="428.1300048828125" height="21.94771385192871"/><one:OEChildren><one:OE creationTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{5778544D-D680-0FAA-328B-B80D7F87C704}{49}{B0}" alignment="left"><one:Table bordersVisible="true" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{5778544D-D680-0FAA-328B-B80D7F87C704}{41}{B0}"><one:Columns><one:Column index="0" width="240.0" isLocked="true"/><one:Column index="1" width="180.0" isLocked="true"/></one:Columns><one:Row objectID="{5778544D-D680-0FAA-328B-B80D7F87C704}{48}{B0}" lastModifiedTime="2026-09-26T06:33:48.000Z"><one:Cell lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{5778544D-D680-0FAA-328B-B80D7F87C704}{42}{B0}"><one:OEChildren><one:OE creationTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{5778544D-D680-0FAA-328B-B80D7F87C704}{44}{B0}" alignment="left" quickStyleIndex="1"><one:Tag index="0" completed="true" disabled="false" creationDate="2020-01-02T03:04:05.000Z" completionDate="2020-01-02T04:05:06.000Z"/><one:T><![CDATA[<span |
| 3 | AAAAAElFTkSuQmCC | 3 | style='font-weight:bold'>Target &#129408; é</span> <a |
| 4 | </one:Data></one:Image></one:OE><one:OE creationTime="2026-09-13T08:12:59.000Z" lastModifiedTime="2026-09-13T08:12:59.000Z" objectID="{30FDBCD9-EBE5-0C42-3241-15D4E7312FCD}{1}{B0}" alignment="left" style="font-family:Calibri;font-size:11.0pt"><one:T><![CDATA[After the picture]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> | 4 | href="https://example.invalid/tree">Link label</a>]]></one:T></one:OE></one:OEChildren></one:Cell><one:Cell lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{5778544D-D680-0FAA-328B-B80D7F87C704}{45}{B0}"><one:OEChildren><one:OE creationTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{5778544D-D680-0FAA-328B-B80D7F87C704}{47}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Other cell]]></one:T></one:OE></one:OEChildren></one:Cell></one:Row></one:Table></one:OE></one:OEChildren></one:Outline><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{5778544D-D680-0FAA-328B-B80D7F87C704}{50}{B0}"><one:Position x="540.0" y="108.0" z="1"/><one:Size width="99.85796356201171" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{5778544D-D680-0FAA-328B-B80D7F87C704}{52}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Other outline]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/page-copy/cold/read/page-002.navigation.json+1-1| ... | @@ -6,7 +6,7 @@ | ... | @@ -6,7 +6,7 @@ |
| 6 | "h": 600, | 6 | "h": 600, |
| 7 | "error": null, | 7 | "error": null, |
| 8 | "win": { | 8 | "win": { |
| 9 | "title": "Indent bullet subtree - Microsoft OneNote", | 9 | "title": "Move numbered subtree down - Microsoft OneNote", |
| 10 | "class": "Framework::CFrame", | 10 | "class": "Framework::CFrame", |
| 11 | "dialog": false | 11 | "dialog": false |
| 12 | } | 12 | } |
corpus/page-copy/cold/read/page-002.png| Binary files a/corpus/page-copy/cold/read/page-002.png and b/corpus/page-copy/cold/read/page-002.png differ | |||
corpus/page-copy/cold/read/page-002.xml+3-3| ... | @@ -1,5 +1,5 @@ | ... | @@ -1,5 +1,5 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{25E90FFA-6C7E-0A8D-3F8B-FC658F7AAF3B}{1}{B0}" name="Indent bullet subtree" dateTime="2026-09-13T08:12:57.000Z" lastModifiedTime="2026-09-13T08:14:27.000Z" pageLevel="1" lang="en-US"><one:TagDef index="0" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Preserved task"/><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:QuickStyleDef index="1" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Copier" lastModifiedBy="Copier" creationTime="2026-09-13T08:12:57.000Z" lastModifiedTime="2026-09-13T08:12:57.000Z" objectID="{25E90FFA-6C7E-0A8D-3F8B-FC658F7AAF3B}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Indent bullet subtree]]></one:T></one:OE></one:Title><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-13T08:12:58.000Z" objectID="{E70D26B4-5E53-0439-14D6-AB435B274370}{1}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="142.6107788085937" height="67.13858032226562"/><one:OEChildren><one:OE creationTime="2026-09-13T08:12:58.000Z" lastModifiedTime="2026-09-13T08:12:58.000Z" objectID="{C20780E2-91FA-0C79-3266-C1E8843F8C27}{1}{B0}" alignment="left" quickStyleIndex="1"><one:List><one:Bullet bullet="3" fontSize="11.0"/></one:List><one:T><![CDATA[Anchor]]></one:T></one:OE><one:OE creationTime="2026-09-13T08:12:58.000Z" lastModifiedTime="2026-09-13T08:12:58.000Z" objectID="{B9F245E8-A24F-019A-3F3E-540901F3F3F1}{1}{B0}" alignment="left" quickStyleIndex="1"><one:Tag index="0" completed="true" disabled="false" creationDate="2020-01-02T03:04:05.000Z" completionDate="2020-01-02T04:05:06.000Z"/><one:List><one:Bullet bullet="3" fontSize="11.0"/></one:List><one:T><![CDATA[<span | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{25A63247-8FFB-0C98-22D9-2B99C3661F5A}{1}{B0}" name="Move numbered subtree down" dateTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:50:56.000Z" pageLevel="1" lang="en-US"><one:TagDef index="0" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Preserved task"/><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:QuickStyleDef index="1" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Copier" lastModifiedBy="Copier" creationTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{25A63247-8FFB-0C98-22D9-2B99C3661F5A}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Move numbered subtree down]]></one:T></one:OE></one:Title><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{5778544D-D680-0FAA-328B-B80D7F87C704}{97}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="142.6107788085937" height="67.13858032226562"/><one:OEChildren><one:OE creationTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{5778544D-D680-0FAA-328B-B80D7F87C704}{99}{B0}" alignment="left" quickStyleIndex="1"><one:List><one:Number numberSequence="1" numberFormat="##." fontSize="11.0" font="Calibri" text="I."/></one:List><one:T><![CDATA[Anchor]]></one:T></one:OE><one:OE creationTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{5778544D-D680-0FAA-328B-B80D7F87C704}{101}{B0}" alignment="left" quickStyleIndex="1"><one:Tag index="0" completed="true" disabled="false" creationDate="2020-01-02T03:04:05.000Z" completionDate="2020-01-02T04:05:06.000Z"/><one:List><one:Number numberSequence="1" numberFormat="##." fontSize="11.0" font="Calibri" text="II."/></one:List><one:T><![CDATA[<span |
| 3 | style='font-weight:bold'>Target &#129408; é</span> <a | 3 | style='font-weight:bold'>Target &#129408; é</span> <a |
| 4 | href="https://example.invalid/tree">Link label</a>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-13T08:12:58.000Z" lastModifiedTime="2026-09-13T08:12:58.000Z" objectID="{F762F81C-0C7D-0D7E-318B-18934A658911}{1}{B0}" alignment="left" quickStyleIndex="1"><one:T style="font-family:Calibri;font-size:11.0pt"><![CDATA[<span | 4 | href="https://example.invalid/tree">Link label</a>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{5778544D-D680-0FAA-328B-B80D7F87C704}{103}{B0}" alignment="left" quickStyleIndex="1"><one:T style="font-family:Calibri;font-size:11.0pt"><![CDATA[<span |
| 5 | style='font-style:italic'>Child</span>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-13T08:12:58.000Z" lastModifiedTime="2026-09-13T08:12:58.000Z" objectID="{25F7C65A-45FD-03F5-31CF-DCA9A335FC64}{1}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Grandchild]]></one:T></one:OE></one:OEChildren></one:OE></one:OEChildren></one:OE><one:OE creationTime="2026-09-13T08:12:58.000Z" lastModifiedTime="2026-09-13T08:12:58.000Z" objectID="{91205826-B132-0A1C-0828-370A55059BE9}{1}{B0}" alignment="left" quickStyleIndex="1"><one:List><one:Bullet bullet="3" fontSize="11.0"/></one:List><one:T><![CDATA[Trailing sibling]]></one:T></one:OE></one:OEChildren></one:Outline><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-13T08:12:58.000Z" objectID="{7E8151C3-CE0D-083C-29FA-66EB4C60734C}{1}{B0}"><one:Position x="540.0" y="108.0" z="1"/><one:Size width="99.85796356201171" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-13T08:12:58.000Z" lastModifiedTime="2026-09-13T08:12:58.000Z" objectID="{A50B7E04-F685-06D4-3602-48EC3C9D5381}{1}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Other outline]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> | 5 | style='font-style:italic'>Child</span>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{5778544D-D680-0FAA-328B-B80D7F87C704}{105}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Grandchild]]></one:T></one:OE></one:OEChildren></one:OE></one:OEChildren></one:OE><one:OE creationTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{5778544D-D680-0FAA-328B-B80D7F87C704}{107}{B0}" alignment="left" quickStyleIndex="1"><one:List><one:Number numberSequence="1" numberFormat="##." fontSize="11.0" font="Calibri" text="III."/></one:List><one:T><![CDATA[Trailing sibling]]></one:T></one:OE></one:OEChildren></one:Outline><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{5778544D-D680-0FAA-328B-B80D7F87C704}{108}{B0}"><one:Position x="540.0" y="108.0" z="1"/><one:Size width="99.85796356201171" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{5778544D-D680-0FAA-328B-B80D7F87C704}{110}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Other outline]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/page-copy/cold/read/page-003.navigation.json+1-1| ... | @@ -6,7 +6,7 @@ | ... | @@ -6,7 +6,7 @@ |
| 6 | "h": 600, | 6 | "h": 600, |
| 7 | "error": null, | 7 | "error": null, |
| 8 | "win": { | 8 | "win": { |
| 9 | "title": "Bullet item - Microsoft OneNote", | 9 | "title": "Equations below - Microsoft OneNote", |
| 10 | "class": "Framework::CFrame", | 10 | "class": "Framework::CFrame", |
| 11 | "dialog": false | 11 | "dialog": false |
| 12 | } | 12 | } |
corpus/page-copy/cold/read/page-003.png| Binary files a/corpus/page-copy/cold/read/page-003.png and b/corpus/page-copy/cold/read/page-003.png differ | |||
corpus/page-copy/cold/read/page-003.xml+2-1| ... | @@ -1,2 +1,3 @@ | ... | @@ -1,2 +1,3 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{35E45165-B253-07BF-1324-6FC7DCE3552B}{1}{B0}" name="Bullet item" dateTime="2026-09-13T08:12:59.000Z" lastModifiedTime="2026-09-13T08:14:27.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Copier" lastModifiedBy="Copier" creationTime="2026-09-13T08:12:59.000Z" lastModifiedTime="2026-09-13T08:12:59.000Z" objectID="{35E45165-B253-07BF-1324-6FC7DCE3552B}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Bullet item]]></one:T></one:OE></one:Title><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-13T08:13:00.000Z" objectID="{1030C98D-AF8F-01AF-0FE4-4821037C552D}{1}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="174.7353515625" height="93.99401092529296"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-13T08:13:00.000Z" lastModifiedTime="2026-09-13T08:13:00.000Z" objectID="{41E8A28A-ADD0-072D-0780-D5A47F095985}{1}{B0}" alignment="left"><one:List><one:Bullet bullet="3" fontSize="11.0"/></one:List><one:T><![CDATA[Bullet item]]></one:T></one:OE><one:OE creationTime="2026-09-13T08:13:00.000Z" lastModifiedTime="2026-09-13T08:13:00.000Z" objectID="{45462BD4-B2B9-0403-1A75-2A2CFE1C6368}{1}{B0}" alignment="left"><one:List><one:Number numberSequence="1" numberFormat="##." fontSize="11.0" font="Calibri" text="I."/></one:List><one:T><![CDATA[First numbered]]></one:T></one:OE><one:OE creationTime="2026-09-13T08:13:00.000Z" lastModifiedTime="2026-09-13T08:13:00.000Z" objectID="{0547114E-6093-0FBB-0257-A78B4D838BFB}{1}{B0}" alignment="left"><one:List><one:Number numberSequence="1" numberFormat="##." fontSize="11.0" font="Calibri" text="II."/></one:List><one:T><![CDATA[Second numbered]]></one:T><one:OEChildren><one:OE creationTime="2026-09-13T08:13:00.000Z" lastModifiedTime="2026-09-13T08:13:00.000Z" objectID="{BBD74A7A-615E-04F9-0758-5D319A106A96}{1}{B0}" alignment="left"><one:List><one:Number numberSequence="1" numberFormat="##." fontSize="11.0" font="Calibri" text="I."/></one:List><one:T><![CDATA[Nested numbered]]></one:T></one:OE></one:OEChildren></one:OE><one:OE creationTime="2026-09-13T08:13:00.000Z" lastModifiedTime="2026-09-13T08:13:00.000Z" objectID="{73CB0260-61B9-026E-068D-9106DD4C284F}{1}{B0}" alignment="left"><one:List><one:Number numberSequence="1" numberFormat="##." fontSize="11.0" font="Calibri" text="III."/></one:List><one:T><![CDATA[Restarted at three]]></one:T><one:OEChildren><one:OE creationTime="2026-09-13T08:13:00.000Z" lastModifiedTime="2026-09-13T08:13:00.000Z" objectID="{D4851817-9021-0C2E-1FB9-78CACB7483E5}{1}{B0}" alignment="left"><one:T><![CDATA[Nested plain]]></one:T></one:OE></one:OEChildren></one:OE><one:OE creationTime="2026-09-13T08:13:00.000Z" lastModifiedTime="2026-09-13T08:13:00.000Z" objectID="{7F1ACAD9-03E3-0D2C-0854-83C7B46AA88A}{1}{B0}" alignment="left"><one:T><![CDATA[Plain again]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{2C4189F4-1EE4-047C-160D-979FD528FBCF}{1}{B0}" name="Equations below" dateTime="2026-09-26T06:33:49.000Z" lastModifiedTime="2026-09-26T06:50:56.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Copier" lastModifiedBy="Copier" creationTime="2026-09-26T06:33:49.000Z" lastModifiedTime="2026-09-26T06:33:49.000Z" objectID="{2C4189F4-1EE4-047C-160D-979FD528FBCF}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Equations below]]></one:T></one:OE></one:Title><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-26T06:33:50.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{182}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="140.9678649902344" height="208.9916534423828"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-26T06:33:50.000Z" lastModifiedTime="2026-09-26T06:33:50.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{184}{B0}" alignment="left"><one:T><![CDATA[Equations below]]></one:T></one:OE><one:OE creationTime="2026-09-26T06:33:50.000Z" lastModifiedTime="2026-09-26T06:33:50.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{186}{B0}" alignment="left" style="font-family:'Cambria Math';font-size:11.0pt"><one:T><![CDATA[<!--[if mathML]><mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" display="block"><mml:msup><mml:mi>x</mml:mi><mml:mn>2</mml:mn></mml:msup><mml:mo>+</mml:mo><mml:msup><mml:mi>y</mml:mi><mml:mn>2</mml:mn></mml:msup><mml:mo>=</mml:mo><mml:msup><mml:mi>z</mml:mi><mml:mn>2</mml:mn></mml:msup></mml:math><![endif]-->]]></one:T></one:OE><one:OE creationTime="2026-09-26T06:33:50.000Z" lastModifiedTime="2026-09-26T06:33:50.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{188}{B0}" alignment="left" style="font-family:'Cambria Math';font-size:11.0pt"><one:T><![CDATA[<!--[if mathML]><mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" display="block"><mml:mfrac><mml:mrow><mml:mi>a</mml:mi><mml:mo>+</mml:mo><mml:mi>b</mml:mi></mml:mrow><mml:mrow><mml:mi>c</mml:mi><mml:mo>+</mml:mo><mml:mi>d</mml:mi></mml:mrow></mml:mfrac></mml:math><![endif]-->]]></one:T></one:OE><one:OE creationTime="2026-09-26T06:33:50.000Z" lastModifiedTime="2026-09-26T06:33:50.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{190}{B0}" alignment="left" style="font-size:11.0pt"><one:T><![CDATA[<span |
| 3 | style='font-family:Calibri' lang=en-US>Inline </span><!--[if mathML]><mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML"><mml:mi>&#120572;</mml:mi><mml:mo>+</mml:mo><mml:mi>&#120573;</mml:mi></mml:math><![endif]-->]]></one:T></one:OE><one:OE creationTime="2026-09-26T06:33:50.000Z" lastModifiedTime="2026-09-26T06:33:50.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{192}{B0}" alignment="left" style="font-family:'Cambria Math';font-size:11.0pt"><one:T><![CDATA[<!--[if mathML]><mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" display="block"><mml:msub><mml:mi>a</mml:mi><mml:mn>1</mml:mn></mml:msub><mml:mo>+</mml:mo><mml:msub><mml:mi>b</mml:mi><mml:mn>2</mml:mn></mml:msub></mml:math><![endif]-->]]></one:T></one:OE><one:OE creationTime="2026-09-26T06:33:50.000Z" lastModifiedTime="2026-09-26T06:33:50.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{194}{B0}" alignment="left" style="font-family:'Cambria Math';font-size:11.0pt"><one:T><![CDATA[<!--[if mathML]><mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" display="block"><mml:msubsup><mml:mi>x</mml:mi><mml:mi>i</mml:mi><mml:mn>2</mml:mn></mml:msubsup></mml:math><![endif]-->]]></one:T></one:OE><one:OE creationTime="2026-09-26T06:33:50.000Z" lastModifiedTime="2026-09-26T06:33:50.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{196}{B0}" alignment="left" style="font-family:'Cambria Math';font-size:11.0pt"><one:T><![CDATA[<!--[if mathML]><mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" display="block"><mml:mfenced><mml:mrow><mml:mi>a</mml:mi><mml:mo>+</mml:mo><mml:mi>b</mml:mi></mml:mrow></mml:mfenced></mml:math><![endif]-->]]></one:T></one:OE><one:OE creationTime="2026-09-26T06:33:50.000Z" lastModifiedTime="2026-09-26T06:33:50.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{198}{B0}" alignment="left" style="font-family:'Cambria Math';font-size:11.0pt"><one:T><![CDATA[<!--[if mathML]><mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" display="block"><mml:msubsup><mml:mo stretchy="false">∫</mml:mo><mml:mn>0</mml:mn><mml:mn>1</mml:mn></mml:msubsup><mml:mrow><mml:mi>x</mml:mi><mml:mo>&nbsp;</mml:mo><mml:mi>d</mml:mi><mml:mi>x</mml:mi><mml:mo>&nbsp;</mml:mo></mml:mrow></mml:math><![endif]-->]]></one:T></one:OE><one:OE creationTime="2026-09-26T06:33:50.000Z" lastModifiedTime="2026-09-26T06:33:50.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{200}{B0}" alignment="left" style="font-family:'Cambria Math';font-size:11.0pt"><one:T><![CDATA[<!--[if mathML]><mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" display="block"><mml:munderover><mml:mo stretchy="false">∑</mml:mo><mml:mrow><mml:mi>i</mml:mi><mml:mo>=</mml:mo><mml:mn>1</mml:mn></mml:mrow><mml:mi>n</mml:mi></mml:munderover><mml:mrow><mml:mi>i</mml:mi><mml:mo>&nbsp;</mml:mo></mml:mrow></mml:math><![endif]-->]]></one:T></one:OE><one:OE creationTime="2026-09-26T06:33:50.000Z" lastModifiedTime="2026-09-26T06:33:50.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{202}{B0}" alignment="left" style="font-family:'Cambria Math';font-size:11.0pt"><one:T><![CDATA[<!--[if mathML]><mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" display="block"><mml:msup><mml:mi>x</mml:mi><mml:mn>2</mml:mn></mml:msup><mml:mo>+</mml:mo><mml:mn>1</mml:mn></mml:math><![endif]-->]]></one:T></one:OE><one:OE creationTime="2026-09-26T06:33:50.000Z" lastModifiedTime="2026-09-26T06:33:50.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{204}{B0}" alignment="left" style="font-family:'Cambria Math';font-size:11.0pt"><one:T><![CDATA[<!--[if mathML]><mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" display="block"><mml:mfrac><mml:mrow><mml:mi>a</mml:mi><mml:mo>+</mml:mo><mml:mi>b</mml:mi></mml:mrow><mml:mrow><mml:msup><mml:mi>c</mml:mi><mml:mn>2</mml:mn></mml:msup></mml:mrow></mml:mfrac></mml:math><![endif]-->]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/page-copy/cold/read/page-004.navigation.json+1-1| ... | @@ -6,7 +6,7 @@ | ... | @@ -6,7 +6,7 @@ |
| 6 | "h": 600, | 6 | "h": 600, |
| 7 | "error": null, | 7 | "error": null, |
| 8 | "win": { | 8 | "win": { |
| 9 | "title": "Delete sole cell paragraph - Microsoft OneNote", | 9 | "title": "Indent bullet subtree - Microsoft OneNote", |
| 10 | "class": "Framework::CFrame", | 10 | "class": "Framework::CFrame", |
| 11 | "dialog": false | 11 | "dialog": false |
| 12 | } | 12 | } |
corpus/page-copy/cold/read/page-004.png| Binary files a/corpus/page-copy/cold/read/page-004.png and b/corpus/page-copy/cold/read/page-004.png differ | |||
corpus/page-copy/cold/read/page-004.xml+3-2| ... | @@ -1,4 +1,5 @@ | ... | @@ -1,4 +1,5 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{493E9D0C-5976-0886-2ADB-636A30B81D20}{1}{B0}" name="Delete sole cell paragraph" dateTime="2026-09-13T08:12:56.000Z" lastModifiedTime="2026-09-13T08:14:27.000Z" pageLevel="1" lang="en-US"><one:TagDef index="0" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Preserved task"/><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:QuickStyleDef index="1" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Copier" lastModifiedBy="Copier" creationTime="2026-09-13T08:12:56.000Z" lastModifiedTime="2026-09-13T08:12:56.000Z" objectID="{493E9D0C-5976-0886-2ADB-636A30B81D20}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Delete sole cell paragraph]]></one:T></one:OE></one:Title><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-13T08:12:57.000Z" objectID="{9721CF72-8007-0180-29C3-D0F422003C1B}{1}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="428.1300048828125" height="21.94771385192871"/><one:OEChildren><one:OE creationTime="2026-09-13T08:12:56.000Z" lastModifiedTime="2026-09-13T08:12:57.000Z" objectID="{76A5FFBC-C8EC-0E30-2663-51714300B179}{1}{B0}" alignment="left"><one:Table bordersVisible="true" lastModifiedTime="2026-09-13T08:12:57.000Z" objectID="{EFDBE2D8-39D5-0EDF-075C-26A7D704FD28}{1}{B0}"><one:Columns><one:Column index="0" width="240.0" isLocked="true"/><one:Column index="1" width="180.0" isLocked="true"/></one:Columns><one:Row objectID="{8D1D5F63-2DD1-0274-04FA-38A73347200A}{1}{B0}" lastModifiedTime="2026-09-13T08:12:57.000Z"><one:Cell lastModifiedTime="2026-09-13T08:12:57.000Z" objectID="{7258A0AD-D622-0854-0122-8EBEB7B70203}{1}{B0}"><one:OEChildren><one:OE creationTime="2026-09-13T08:12:56.000Z" lastModifiedTime="2026-09-13T08:12:57.000Z" objectID="{63217049-E82C-0F04-08AE-9CB184551B5D}{1}{B0}" alignment="left" quickStyleIndex="1"><one:Tag index="0" completed="true" disabled="false" creationDate="2020-01-02T03:04:05.000Z" completionDate="2020-01-02T04:05:06.000Z"/><one:T><![CDATA[<span | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{364CF1D3-423A-0B9F-1019-BE369236958F}{1}{B0}" name="Indent bullet subtree" dateTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:50:56.000Z" pageLevel="1" lang="en-US"><one:TagDef index="0" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Preserved task"/><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:QuickStyleDef index="1" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Copier" lastModifiedBy="Copier" creationTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{364CF1D3-423A-0B9F-1019-BE369236958F}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Indent bullet subtree]]></one:T></one:OE></one:Title><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-26T06:33:49.000Z" objectID="{5778544D-D680-0FAA-328B-B80D7F87C704}{213}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="142.6107788085937" height="67.13858032226562"/><one:OEChildren><one:OE creationTime="2026-09-26T06:33:49.000Z" lastModifiedTime="2026-09-26T06:33:49.000Z" objectID="{5778544D-D680-0FAA-328B-B80D7F87C704}{215}{B0}" alignment="left" quickStyleIndex="1"><one:List><one:Bullet bullet="3" fontSize="11.0"/></one:List><one:T><![CDATA[Anchor]]></one:T></one:OE><one:OE creationTime="2026-09-26T06:33:49.000Z" lastModifiedTime="2026-09-26T06:33:49.000Z" objectID="{5778544D-D680-0FAA-328B-B80D7F87C704}{217}{B0}" alignment="left" quickStyleIndex="1"><one:Tag index="0" completed="true" disabled="false" creationDate="2020-01-02T03:04:05.000Z" completionDate="2020-01-02T04:05:06.000Z"/><one:List><one:Bullet bullet="3" fontSize="11.0"/></one:List><one:T><![CDATA[<span |
| 3 | style='font-weight:bold'>Target &#129408; é</span> <a | 3 | style='font-weight:bold'>Target &#129408; é</span> <a |
| 4 | href="https://example.invalid/tree">Link label</a>]]></one:T></one:OE></one:OEChildren></one:Cell><one:Cell lastModifiedTime="2026-09-13T08:12:56.000Z" objectID="{05B95457-4B57-0270-392F-6988B33CE2A8}{1}{B0}"><one:OEChildren><one:OE creationTime="2026-09-13T08:12:56.000Z" lastModifiedTime="2026-09-13T08:12:56.000Z" objectID="{D490ACFE-4E2D-09CE-1F5F-0866F94CA145}{1}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Other cell]]></one:T></one:OE></one:OEChildren></one:Cell></one:Row></one:Table></one:OE></one:OEChildren></one:Outline><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-13T08:12:56.000Z" objectID="{4D870AE7-2513-0CB4-3066-2217A47AB044}{1}{B0}"><one:Position x="540.0" y="108.0" z="1"/><one:Size width="99.85796356201171" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-13T08:12:56.000Z" lastModifiedTime="2026-09-13T08:12:56.000Z" objectID="{EFA33014-C3C9-0764-0876-5357D230EE70}{1}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Other outline]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> | 4 | href="https://example.invalid/tree">Link label</a>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-26T06:33:49.000Z" lastModifiedTime="2026-09-26T06:33:49.000Z" objectID="{5778544D-D680-0FAA-328B-B80D7F87C704}{219}{B0}" alignment="left" quickStyleIndex="1"><one:T style="font-family:Calibri;font-size:11.0pt"><![CDATA[<span |
| 5 | style='font-style:italic'>Child</span>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-26T06:33:49.000Z" lastModifiedTime="2026-09-26T06:33:49.000Z" objectID="{5778544D-D680-0FAA-328B-B80D7F87C704}{221}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Grandchild]]></one:T></one:OE></one:OEChildren></one:OE></one:OEChildren></one:OE><one:OE creationTime="2026-09-26T06:33:49.000Z" lastModifiedTime="2026-09-26T06:33:49.000Z" objectID="{5778544D-D680-0FAA-328B-B80D7F87C704}{223}{B0}" alignment="left" quickStyleIndex="1"><one:List><one:Bullet bullet="3" fontSize="11.0"/></one:List><one:T><![CDATA[Trailing sibling]]></one:T></one:OE></one:OEChildren></one:Outline><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-26T06:33:49.000Z" objectID="{5778544D-D680-0FAA-328B-B80D7F87C704}{224}{B0}"><one:Position x="540.0" y="108.0" z="1"/><one:Size width="99.85796356201171" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-26T06:33:49.000Z" lastModifiedTime="2026-09-26T06:33:49.000Z" objectID="{5778544D-D680-0FAA-328B-B80D7F87C704}{226}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Other outline]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/page-copy/cold/read/page-005.navigation.json+1-1| ... | @@ -6,7 +6,7 @@ | ... | @@ -6,7 +6,7 @@ |
| 6 | "h": 600, | 6 | "h": 600, |
| 7 | "error": null, | 7 | "error": null, |
| 8 | "win": { | 8 | "win": { |
| 9 | "title": "Indent first subtree - Microsoft OneNote", | 9 | "title": "Destination - Microsoft OneNote", |
| 10 | "class": "Framework::CFrame", | 10 | "class": "Framework::CFrame", |
| 11 | "dialog": false | 11 | "dialog": false |
| 12 | } | 12 | } |
corpus/page-copy/cold/read/page-005.png| Binary files a/corpus/page-copy/cold/read/page-005.png and b/corpus/page-copy/cold/read/page-005.png differ | |||
corpus/page-copy/cold/read/page-005.xml+1-4| ... | @@ -1,5 +1,2 @@ | ... | @@ -1,5 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{55B596AA-4540-085D-142D-9F788431ACE5}{1}{B0}" name="Indent first subtree" dateTime="2026-09-13T08:12:55.000Z" lastModifiedTime="2026-09-13T08:14:27.000Z" pageLevel="1" lang="en-US"><one:TagDef index="0" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Preserved task"/><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:QuickStyleDef index="1" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Copier" lastModifiedBy="Copier" creationTime="2026-09-13T08:12:55.000Z" lastModifiedTime="2026-09-13T08:12:55.000Z" objectID="{55B596AA-4540-085D-142D-9F788431ACE5}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Indent first subtree]]></one:T></one:OE></one:Title><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-13T08:12:56.000Z" objectID="{5BD6B4E1-21C1-08FD-38E8-4C7ADD81FBB1}{1}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="142.6107788085937" height="53.71086883544922"/><one:OEChildren><one:OE creationTime="2026-09-13T08:12:56.000Z" lastModifiedTime="2026-09-13T08:12:56.000Z" objectID="{8B6DEB92-F345-02E1-1FB6-90BBC691E721}{1}{B0}" alignment="left" quickStyleIndex="1"><one:Tag index="0" completed="true" disabled="false" creationDate="2020-01-02T03:04:05.000Z" completionDate="2020-01-02T04:05:06.000Z"/><one:T><![CDATA[<span | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{64D50BBC-095D-0B40-049D-EE63CB34BC4D}{1}{B0}" name="Destination" dateTime="2026-09-26T06:33:47.000Z" lastModifiedTime="2026-09-26T06:50:55.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-26T06:33:47.000Z" objectID="{D9F74361-435D-0594-006D-92ECD10C11D3}{23}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="118.3393707275391" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-26T06:33:47.000Z" lastModifiedTime="2026-09-26T06:33:47.000Z" objectID="{D9F74361-435D-0594-006D-92ECD10C11D3}{24}{B0}" alignment="left"><one:T><![CDATA[Destination]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
| 3 | style='font-weight:bold'>Target &#129408; é</span> <a | ||
| 4 | href="https://example.invalid/tree">Link label</a>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-13T08:12:56.000Z" lastModifiedTime="2026-09-13T08:12:56.000Z" objectID="{365B8C25-37F9-0C75-1AD0-A969AB393EEC}{1}{B0}" alignment="left" quickStyleIndex="1"><one:T style="font-family:Calibri;font-size:11.0pt"><![CDATA[<span | ||
| 5 | style='font-style:italic'>Child</span>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-13T08:12:56.000Z" lastModifiedTime="2026-09-13T08:12:56.000Z" objectID="{53D9A482-FB38-09F8-318E-E7A2A0EA655A}{1}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Grandchild]]></one:T></one:OE></one:OEChildren></one:OE></one:OEChildren></one:OE><one:OE creationTime="2026-09-13T08:12:56.000Z" lastModifiedTime="2026-09-13T08:12:56.000Z" objectID="{C7BC8628-6A45-0A7E-10CA-1A957E36B827}{1}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Trailing sibling]]></one:T></one:OE></one:OEChildren></one:Outline><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-13T08:12:56.000Z" objectID="{B5066461-BBDD-09E8-223D-5B839E2A8637}{1}{B0}"><one:Position x="540.0" y="108.0" z="1"/><one:Size width="99.85796356201171" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-13T08:12:56.000Z" lastModifiedTime="2026-09-13T08:12:56.000Z" objectID="{EDB23709-B01D-0CD2-08A7-A9EB953087ED}{1}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Other outline]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/page-copy/cold/read/page-006.navigation.json+1-1| ... | @@ -6,7 +6,7 @@ | ... | @@ -6,7 +6,7 @@ |
| 6 | "h": 600, | 6 | "h": 600, |
| 7 | "error": null, | 7 | "error": null, |
| 8 | "win": { | 8 | "win": { |
| 9 | "title": "Bold \ud83e\udd80 italic e\u0301 color \u6771\u4eac - Microsoft OneNote", | 9 | "title": "Move cell subtree down - Microsoft OneNote", |
| 10 | "class": "Framework::CFrame", | 10 | "class": "Framework::CFrame", |
| 11 | "dialog": false | 11 | "dialog": false |
| 12 | } | 12 | } |
corpus/page-copy/cold/read/page-006.png| Binary files a/corpus/page-copy/cold/read/page-006.png and b/corpus/page-copy/cold/read/page-006.png differ | |||
corpus/page-copy/cold/read/page-006.xml+4-19| ... | @@ -1,20 +1,5 @@ | ... | @@ -1,20 +1,5 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{5E04C302-1E83-0CCD-1715-B0A2566E0191}{1}{B0}" name="Bold 🦀 italic é color 東京" dateTime="2026-09-13T08:12:55.000Z" lastModifiedTime="2026-09-13T08:14:26.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:QuickStyleDef index="1" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Copier" lastModifiedBy="Copier" creationTime="2026-09-13T08:12:55.000Z" lastModifiedTime="2026-09-13T08:12:55.000Z" objectID="{5E04C302-1E83-0CCD-1715-B0A2566E0191}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Bold &#129408; italic é color 東京]]></one:T></one:OE></one:Title><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-13T08:12:56.000Z" objectID="{B63306AF-2B95-0428-2EB2-67405305D5CD}{1}{B0}"><one:Position x="36.0" y="14.40000057220459" z="0"/><one:Size width="127.5136947631836" height="42.03070831298828"/><one:OEChildren><one:OE creationTime="2026-09-13T08:12:55.000Z" lastModifiedTime="2026-09-13T08:12:56.000Z" objectID="{07F96F7E-EE93-0108-00D0-9E4BE9011C40}{1}{B0}" alignment="left"><one:T><![CDATA[<span style='font-weight:bold; | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{6E15C33B-F88D-0966-239D-1EB519063D70}{1}{B0}" name="Move cell subtree down" dateTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:50:56.000Z" pageLevel="1" lang="en-US"><one:TagDef index="0" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Preserved task"/><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:QuickStyleDef index="1" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Copier" lastModifiedBy="Copier" creationTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{6E15C33B-F88D-0966-239D-1EB519063D70}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Move cell subtree down]]></one:T></one:OE></one:Title><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-26T06:33:49.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{43}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="428.1300048828125" height="62.23086929321289"/><one:OEChildren><one:OE creationTime="2026-09-26T06:33:49.000Z" lastModifiedTime="2026-09-26T06:33:49.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{58}{B0}" alignment="left"><one:Table bordersVisible="true" lastModifiedTime="2026-09-26T06:33:49.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{44}{B0}"><one:Columns><one:Column index="0" width="240.0" isLocked="true"/><one:Column index="1" width="180.0" isLocked="true"/></one:Columns><one:Row objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{57}{B0}" lastModifiedTime="2026-09-26T06:33:49.000Z"><one:Cell lastModifiedTime="2026-09-26T06:33:49.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{45}{B0}"><one:OEChildren><one:OE creationTime="2026-09-26T06:33:49.000Z" lastModifiedTime="2026-09-26T06:33:49.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{47}{B0}" alignment="left" quickStyleIndex="1"><one:Tag index="0" completed="true" disabled="false" creationDate="2020-01-02T03:04:05.000Z" completionDate="2020-01-02T04:05:06.000Z"/><one:T><![CDATA[<span |
| 3 | text-decoration:underline;font-family:Calibri;font-size:11.0pt'>Bold</span><span | 3 | style='font-weight:bold'>Target &#129408; é</span> <a |
| 4 | style='font-family:Calibri;font-size:11.0pt'> </span><span style='font-family: | 4 | href="https://example.invalid/tree">Link label</a>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-26T06:33:49.000Z" lastModifiedTime="2026-09-26T06:33:49.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{49}{B0}" alignment="left" quickStyleIndex="1"><one:T style="font-family:Calibri;font-size:11.0pt"><![CDATA[<span |
| 5 | Arial;font-size:13.5pt'>&#129408;</span><span style='font-family:Calibri; | 5 | style='font-style:italic'>Child</span>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-26T06:33:49.000Z" lastModifiedTime="2026-09-26T06:33:49.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{51}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Grandchild]]></one:T></one:OE></one:OEChildren></one:OE></one:OEChildren></one:OE><one:OE creationTime="2026-09-26T06:33:49.000Z" lastModifiedTime="2026-09-26T06:33:49.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{53}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Cell tail]]></one:T></one:OE></one:OEChildren></one:Cell><one:Cell lastModifiedTime="2026-09-26T06:33:49.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{54}{B0}"><one:OEChildren><one:OE creationTime="2026-09-26T06:33:49.000Z" lastModifiedTime="2026-09-26T06:33:49.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{56}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Other cell]]></one:T></one:OE></one:OEChildren></one:Cell></one:Row></one:Table></one:OE></one:OEChildren></one:Outline><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-26T06:33:49.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{59}{B0}"><one:Position x="540.0" y="108.0" z="1"/><one:Size width="99.85796356201171" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-26T06:33:49.000Z" lastModifiedTime="2026-09-26T06:33:49.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{61}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Other outline]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
| 6 | font-size:11.0pt'> </span><span style='font-style:italic;text-decoration:line-through; | ||
| 7 | font-family:Calibri;font-size:11.0pt'>italic</span><span style='font-family: | ||
| 8 | Calibri;font-size:11.0pt'> </span><span style='vertical-align:super;font-family: | ||
| 9 | Calibri;font-size:11.0pt'>é</span><span style='font-family:Calibri;font-size: | ||
| 10 | 11.0pt'> </span><span style='font-family:Calibri;font-size:11.0pt;color:#0C2238; | ||
| 11 | background:yellow;mso-highlight:yellow'>color</span><span style='font-family: | ||
| 12 | Calibri;font-size:11.0pt'> </span><span style='vertical-align:sub;font-family: | ||
| 13 | Calibri;font-size:11.0pt'>東京</span><span style='font-family:Calibri;font-size: | ||
| 14 | 11.0pt'><br> | ||
| 15 | End</span>]]></one:T></one:OE><one:OE creationTime="2026-09-13T08:12:55.000Z" lastModifiedTime="2026-09-13T08:12:55.000Z" objectID="{EEA4A13A-3CBD-04DA-02B1-47CF8F1BE77E}{1}{B0}" alignment="left" quickStyleIndex="1" style="font-size:11.0pt;color:#1F4E79"><one:T><![CDATA[<span | ||
| 16 | style='font-weight:bold;font-family:Calibri' lang=en-US>Fictitious: café, </span><span | ||
| 17 | style='font-weight:bold;font-family:SimSun' lang=en-US>東京</span><span | ||
| 18 | style='font-weight:bold;font-family:Calibri' lang=en-US>, </span><span | ||
| 19 | style='font-weight:bold;font-family:Arial;direction:rtl;unicode-bidi:embed' | ||
| 20 | lang=ar-SA>مرحبا</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/page-copy/cold/read/page-007.navigation.json+1-1| ... | @@ -6,7 +6,7 @@ | ... | @@ -6,7 +6,7 @@ |
| 6 | "h": 600, | 6 | "h": 600, |
| 7 | "error": null, | 7 | "error": null, |
| 8 | "win": { | 8 | "win": { |
| 9 | "title": "Before the file - Microsoft OneNote", | 9 | "title": "Delete cell subtree - Microsoft OneNote", |
| 10 | "class": "Framework::CFrame", | 10 | "class": "Framework::CFrame", |
| 11 | "dialog": false | 11 | "dialog": false |
| 12 | } | 12 | } |
corpus/page-copy/cold/read/page-007.png| Binary files a/corpus/page-copy/cold/read/page-007.png and b/corpus/page-copy/cold/read/page-007.png differ | |||
corpus/page-copy/cold/read/page-007.xml+4-1| ... | @@ -1,2 +1,5 @@ | ... | @@ -1,2 +1,5 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{62E12C49-D5FE-0850-17D5-B4DC95B736E9}{1}{B0}" name="Before the file" dateTime="2026-09-13T08:12:55.000Z" lastModifiedTime="2026-09-13T08:14:26.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Copier" lastModifiedBy="Copier" creationTime="2026-09-13T08:12:55.000Z" lastModifiedTime="2026-09-13T08:12:55.000Z" objectID="{62E12C49-D5FE-0850-17D5-B4DC95B736E9}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Before the file]]></one:T></one:OE></one:Title><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-13T08:12:55.000Z" objectID="{FBCA9C23-1A13-020A-35BC-1365A45C381E}{1}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="130.4619598388672" height="89.85542297363281"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-13T08:12:55.000Z" lastModifiedTime="2026-09-13T08:12:55.000Z" objectID="{2A6BB41D-EDD1-0546-0571-2950BFD2D187}{1}{B0}" alignment="left" style="font-family:Calibri;font-size:11.0pt"><one:T><![CDATA[Before the file]]></one:T></one:OE><one:OE creationTime="2026-09-13T08:12:55.000Z" lastModifiedTime="2026-09-13T08:12:55.000Z" objectID="{9E89195D-B7EC-0AC4-34FD-F47D633F0533}{1}{B0}" alignment="left"><one:InsertedFile pathCache="C:\Users\clover\AppData\Local\Microsoft\OneNote\14.0\OneNoteOfflineCache_Files\6bf086f4-be12-4374-9140-4ddaf36216fe.txt" preferredName="notes 🦀.txt"/></one:OE><one:OE creationTime="2026-09-13T08:12:55.000Z" lastModifiedTime="2026-09-13T08:12:55.000Z" objectID="{48D46AA0-F634-084B-171A-1D72CF298968}{1}{B0}" alignment="left" style="font-family:Calibri;font-size:11.0pt"><one:T><![CDATA[After the file]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{885E9DE1-BDB8-02C4-1534-B3C832F89C02}{1}{B0}" name="Delete cell subtree" dateTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:50:55.000Z" pageLevel="1" lang="en-US"><one:TagDef index="0" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Preserved task"/><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:QuickStyleDef index="1" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Copier" lastModifiedBy="Copier" creationTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{885E9DE1-BDB8-02C4-1534-B3C832F89C02}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Delete cell subtree]]></one:T></one:OE></one:Title><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{229}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="428.1300048828125" height="62.30315780639648"/><one:OEChildren><one:OE creationTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{254}{B0}" alignment="left"><one:Table bordersVisible="true" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{230}{B0}"><one:Columns><one:Column index="0" width="240.0" isLocked="true"/><one:Column index="1" width="180.0" isLocked="true"/></one:Columns><one:Row objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{253}{B0}" lastModifiedTime="2026-09-26T06:33:48.000Z"><one:Cell lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{231}{B0}"><one:OEChildren><one:OE creationTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{233}{B0}" alignment="left" quickStyleIndex="1"><one:Tag index="0" completed="true" disabled="false" creationDate="2020-01-02T03:04:05.000Z" completionDate="2020-01-02T04:05:06.000Z"/><one:T><![CDATA[<span |
| 3 | style='font-weight:bold'>Target &#129408; é</span> <a | ||
| 4 | href="https://example.invalid/tree">Link label</a>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{235}{B0}" alignment="left" quickStyleIndex="1"><one:T style="font-family:Calibri;font-size:11.0pt"><![CDATA[<span | ||
| 5 | style='font-style:italic'>Child</span>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{237}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Grandchild]]></one:T></one:OE></one:OEChildren></one:OE></one:OEChildren></one:OE><one:OE creationTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{239}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Cell tail]]></one:T></one:OE></one:OEChildren></one:Cell><one:Cell lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{240}{B0}"><one:OEChildren><one:OE creationTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{242}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Other cell]]></one:T></one:OE><one:OE creationTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{252}{B0}" alignment="left" style="font-family:Calibri;font-size:11.0pt"><one:Table bordersVisible="true" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{243}{B0}"><one:Columns><one:Column index="0" width="72.0" isLocked="true"/></one:Columns><one:Row objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{247}{B0}" lastModifiedTime="2026-09-26T06:33:48.000Z"><one:Cell lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{244}{B0}"><one:OEChildren><one:OE creationTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{246}{B0}" alignment="left"><one:T><![CDATA[Inner one]]></one:T></one:OE></one:OEChildren></one:Cell></one:Row><one:Row objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{251}{B0}" lastModifiedTime="2026-09-26T06:33:48.000Z"><one:Cell lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{248}{B0}"><one:OEChildren><one:OE creationTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{250}{B0}" alignment="left"><one:T><![CDATA[Inner two]]></one:T></one:OE></one:OEChildren></one:Cell></one:Row></one:Table></one:OE></one:OEChildren></one:Cell></one:Row></one:Table></one:OE></one:OEChildren></one:Outline><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{255}{B0}"><one:Position x="540.0" y="108.0" z="1"/><one:Size width="99.85796356201171" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{5778544D-D680-0FAA-328B-B80D7F87C704}{2}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Other outline]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/page-copy/cold/read/page-008.navigation.json+1-1| ... | @@ -6,7 +6,7 @@ | ... | @@ -6,7 +6,7 @@ |
| 6 | "h": 600, | 6 | "h": 600, |
| 7 | "error": null, | 7 | "error": null, |
| 8 | "win": { | 8 | "win": { |
| 9 | "title": "Delete numbered subtree - Microsoft OneNote", | 9 | "title": "Before the table - Microsoft OneNote", |
| 10 | "class": "Framework::CFrame", | 10 | "class": "Framework::CFrame", |
| 11 | "dialog": false | 11 | "dialog": false |
| 12 | } | 12 | } |
corpus/page-copy/cold/read/page-008.png| Binary files a/corpus/page-copy/cold/read/page-008.png and b/corpus/page-copy/cold/read/page-008.png differ | |||
corpus/page-copy/cold/read/page-008.xml+1-4| ... | @@ -1,5 +1,2 @@ | ... | @@ -1,5 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{6EC9E652-85BD-0524-17E7-232F5951B731}{1}{B0}" name="Delete numbered subtree" dateTime="2026-09-13T08:12:57.000Z" lastModifiedTime="2026-09-13T08:14:27.000Z" pageLevel="1" lang="en-US"><one:TagDef index="0" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Preserved task"/><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:QuickStyleDef index="1" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Copier" lastModifiedBy="Copier" creationTime="2026-09-13T08:12:57.000Z" lastModifiedTime="2026-09-13T08:12:57.000Z" objectID="{6EC9E652-85BD-0524-17E7-232F5951B731}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Delete numbered subtree]]></one:T></one:OE></one:Title><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-13T08:12:58.000Z" objectID="{7D694249-C0B4-02DD-1DC7-3136ED2FFA27}{1}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="142.6107788085937" height="67.13858032226562"/><one:OEChildren><one:OE creationTime="2026-09-13T08:12:57.000Z" lastModifiedTime="2026-09-13T08:12:57.000Z" objectID="{758447BB-8217-0430-1A24-108E47546937}{1}{B0}" alignment="left" quickStyleIndex="1"><one:List><one:Number numberSequence="1" numberFormat="##." fontSize="11.0" font="Calibri" text="I."/></one:List><one:T><![CDATA[Anchor]]></one:T></one:OE><one:OE creationTime="2026-09-13T08:12:57.000Z" lastModifiedTime="2026-09-13T08:12:58.000Z" objectID="{72575BCE-8279-0163-3BD9-443858B25152}{1}{B0}" alignment="left" quickStyleIndex="1"><one:Tag index="0" completed="true" disabled="false" creationDate="2020-01-02T03:04:05.000Z" completionDate="2020-01-02T04:05:06.000Z"/><one:List><one:Number numberSequence="1" numberFormat="##." fontSize="11.0" font="Calibri" text="II."/></one:List><one:T><![CDATA[<span | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{8BD579FF-C812-0827-2B91-968E287CBC55}{1}{B0}" name="Before the table" dateTime="2026-09-26T06:33:47.000Z" lastModifiedTime="2026-09-26T06:50:55.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Copier" lastModifiedBy="Copier" creationTime="2026-09-26T06:33:47.000Z" lastModifiedTime="2026-09-26T06:33:47.000Z" objectID="{8BD579FF-C812-0827-2B91-968E287CBC55}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Before the table]]></one:T></one:OE></one:Title><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{36}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="395.1299438476562" height="67.21086883544922"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{38}{B0}" alignment="left"><one:T><![CDATA[Before the table]]></one:T></one:OE><one:OE creationTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{54}{B0}" alignment="left"><one:Table bordersVisible="true" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{39}{B0}"><one:Columns><one:Column index="0" width="144.0" isLocked="true"/><one:Column index="1" width="216.0" isLocked="true"/></one:Columns><one:Row objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{46}{B0}" lastModifiedTime="2026-09-26T06:33:48.000Z"><one:Cell lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{40}{B0}"><one:OEChildren><one:OE creationTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{42}{B0}" alignment="left"><one:T><![CDATA[Name]]></one:T></one:OE></one:OEChildren></one:Cell><one:Cell lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{43}{B0}"><one:OEChildren><one:OE creationTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{45}{B0}" alignment="left"><one:T><![CDATA[Value]]></one:T></one:OE></one:OEChildren></one:Cell></one:Row><one:Row objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{53}{B0}" lastModifiedTime="2026-09-26T06:33:48.000Z"><one:Cell lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{47}{B0}"><one:OEChildren><one:OE creationTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{49}{B0}" alignment="left"><one:T><![CDATA[Rust &#129408;]]></one:T></one:OE></one:OEChildren></one:Cell><one:Cell lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{50}{B0}"><one:OEChildren><one:OE creationTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{52}{B0}" alignment="left"><one:T><![CDATA[Written natively]]></one:T></one:OE></one:OEChildren></one:Cell></one:Row></one:Table></one:OE><one:OE creationTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{56}{B0}" alignment="left"><one:T><![CDATA[After the table]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
| 3 | style='font-weight:bold'>Target &#129408; é</span> <a | ||
| 4 | href="https://example.invalid/tree">Link label</a>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-13T08:12:57.000Z" lastModifiedTime="2026-09-13T08:12:57.000Z" objectID="{59A91EE5-A5B6-06B1-328F-DCF3FC7EDAAC}{1}{B0}" alignment="left" quickStyleIndex="1"><one:T style="font-family:Calibri;font-size:11.0pt"><![CDATA[<span | ||
| 5 | style='font-style:italic'>Child</span>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-13T08:12:57.000Z" lastModifiedTime="2026-09-13T08:12:57.000Z" objectID="{9B857AAD-2B9A-0AB3-1341-A048617E97BC}{1}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Grandchild]]></one:T></one:OE></one:OEChildren></one:OE></one:OEChildren></one:OE><one:OE creationTime="2026-09-13T08:12:57.000Z" lastModifiedTime="2026-09-13T08:12:57.000Z" objectID="{C4A6021B-B90F-0CD2-2BDB-0BDD084AB0A6}{1}{B0}" alignment="left" quickStyleIndex="1"><one:List><one:Number numberSequence="1" numberFormat="##." fontSize="11.0" font="Calibri" text="III."/></one:List><one:T><![CDATA[Trailing sibling]]></one:T></one:OE></one:OEChildren></one:Outline><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-13T08:12:57.000Z" objectID="{194A9C6A-9CCD-07AA-13B8-DE3F2E45394E}{1}{B0}"><one:Position x="540.0" y="108.0" z="1"/><one:Size width="99.85796356201171" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-13T08:12:57.000Z" lastModifiedTime="2026-09-13T08:12:57.000Z" objectID="{D3A6B926-9395-0950-1BB5-A6C460D39349}{1}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Other outline]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/page-copy/cold/read/page-009.navigation.json+1-1| ... | @@ -6,7 +6,7 @@ | ... | @@ -6,7 +6,7 @@ |
| 6 | "h": 600, | 6 | "h": 600, |
| 7 | "error": null, | 7 | "error": null, |
| 8 | "win": { | 8 | "win": { |
| 9 | "title": "Equations below - Microsoft OneNote", | 9 | "title": "Before the picture - Microsoft OneNote", |
| 10 | "class": "Framework::CFrame", | 10 | "class": "Framework::CFrame", |
| 11 | "dialog": false | 11 | "dialog": false |
| 12 | } | 12 | } |
corpus/page-copy/cold/read/page-009.png| Binary files a/corpus/page-copy/cold/read/page-009.png and b/corpus/page-copy/cold/read/page-009.png differ | |||
corpus/page-copy/cold/read/page-009.xml+3-2| ... | @@ -1,3 +1,4 @@ | ... | @@ -1,3 +1,4 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{78595C91-C5E1-0D03-3A98-A98CA31DE5A8}{1}{B0}" name="Equations below" dateTime="2026-09-13T08:12:59.000Z" lastModifiedTime="2026-09-13T08:14:27.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Copier" lastModifiedBy="Copier" creationTime="2026-09-13T08:12:59.000Z" lastModifiedTime="2026-09-13T08:12:59.000Z" objectID="{78595C91-C5E1-0D03-3A98-A98CA31DE5A8}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Equations below]]></one:T></one:OE></one:Title><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-13T08:13:01.000Z" objectID="{FF465D92-B6F0-0E57-3F42-D3F428E0F163}{1}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="140.9678649902344" height="208.9916534423828"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-13T08:13:01.000Z" lastModifiedTime="2026-09-13T08:13:01.000Z" objectID="{25D933DF-AC4B-0333-0643-3483B2E2652A}{1}{B0}" alignment="left"><one:T><![CDATA[Equations below]]></one:T></one:OE><one:OE creationTime="2026-09-13T08:13:01.000Z" lastModifiedTime="2026-09-13T08:13:01.000Z" objectID="{73426626-83B5-05D8-26F7-5D6887ED2AB5}{1}{B0}" alignment="left" style="font-family:'Cambria Math';font-size:11.0pt"><one:T><![CDATA[<!--[if mathML]><mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" display="block"><mml:msup><mml:mi>x</mml:mi><mml:mn>2</mml:mn></mml:msup><mml:mo>+</mml:mo><mml:msup><mml:mi>y</mml:mi><mml:mn>2</mml:mn></mml:msup><mml:mo>=</mml:mo><mml:msup><mml:mi>z</mml:mi><mml:mn>2</mml:mn></mml:msup></mml:math><![endif]-->]]></one:T></one:OE><one:OE creationTime="2026-09-13T08:13:01.000Z" lastModifiedTime="2026-09-13T08:13:01.000Z" objectID="{45DEE337-060F-078E-1093-87D9A54DF41A}{1}{B0}" alignment="left" style="font-family:'Cambria Math';font-size:11.0pt"><one:T><![CDATA[<!--[if mathML]><mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" display="block"><mml:mfrac><mml:mrow><mml:mi>a</mml:mi><mml:mo>+</mml:mo><mml:mi>b</mml:mi></mml:mrow><mml:mrow><mml:mi>c</mml:mi><mml:mo>+</mml:mo><mml:mi>d</mml:mi></mml:mrow></mml:mfrac></mml:math><![endif]-->]]></one:T></one:OE><one:OE creationTime="2026-09-13T08:13:01.000Z" lastModifiedTime="2026-09-13T08:13:01.000Z" objectID="{68218407-AC5B-0355-295E-667A238EAC19}{1}{B0}" alignment="left" style="font-size:11.0pt"><one:T><![CDATA[<span | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{918C50B7-7848-0240-056C-816588770AB5}{1}{B0}" name="Before the picture" dateTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:50:56.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Copier" lastModifiedBy="Copier" creationTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{918C50B7-7848-0240-056C-816588770AB5}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Before the picture]]></one:T></one:OE></one:Title><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-26T06:33:49.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{78}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="148.3100738525391" height="27.60543251037598"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-26T06:33:49.000Z" lastModifiedTime="2026-09-26T06:33:49.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{80}{B0}" alignment="left" style="font-family:Calibri;font-size:11.0pt"><one:T><![CDATA[Before the picture]]></one:T></one:OE><one:OE creationTime="2026-09-26T06:33:49.000Z" lastModifiedTime="2026-09-26T06:33:49.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{82}{B0}" alignment="left"><one:Image format="png"><one:Data>iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVQI12P4z8AAAAMBAQAY3Y2w |
| 3 | style='font-family:Calibri' lang=en-US>Inline </span><!--[if mathML]><mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML"><mml:mi>&#120572;</mml:mi><mml:mo>+</mml:mo><mml:mi>&#120573;</mml:mi></mml:math><![endif]-->]]></one:T></one:OE><one:OE creationTime="2026-09-13T08:13:01.000Z" lastModifiedTime="2026-09-13T08:13:01.000Z" objectID="{3F067690-E557-00BC-3846-0628BB3D7133}{1}{B0}" alignment="left" style="font-family:'Cambria Math';font-size:11.0pt"><one:T><![CDATA[<!--[if mathML]><mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" display="block"><mml:msub><mml:mi>a</mml:mi><mml:mn>1</mml:mn></mml:msub><mml:mo>+</mml:mo><mml:msub><mml:mi>b</mml:mi><mml:mn>2</mml:mn></mml:msub></mml:math><![endif]-->]]></one:T></one:OE><one:OE creationTime="2026-09-13T08:13:01.000Z" lastModifiedTime="2026-09-13T08:13:01.000Z" objectID="{7156930C-7D25-0CD1-1992-346BE0DD05FD}{1}{B0}" alignment="left" style="font-family:'Cambria Math';font-size:11.0pt"><one:T><![CDATA[<!--[if mathML]><mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" display="block"><mml:msubsup><mml:mi>x</mml:mi><mml:mi>i</mml:mi><mml:mn>2</mml:mn></mml:msubsup></mml:math><![endif]-->]]></one:T></one:OE><one:OE creationTime="2026-09-13T08:13:01.000Z" lastModifiedTime="2026-09-13T08:13:01.000Z" objectID="{1DE19BC8-B93A-02DB-2FA8-92B03D373A73}{1}{B0}" alignment="left" style="font-family:'Cambria Math';font-size:11.0pt"><one:T><![CDATA[<!--[if mathML]><mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" display="block"><mml:mfenced><mml:mrow><mml:mi>a</mml:mi><mml:mo>+</mml:mo><mml:mi>b</mml:mi></mml:mrow></mml:mfenced></mml:math><![endif]-->]]></one:T></one:OE><one:OE creationTime="2026-09-13T08:13:01.000Z" lastModifiedTime="2026-09-13T08:13:01.000Z" objectID="{5C43FBBD-302D-09D2-28A9-18789A71A072}{1}{B0}" alignment="left" style="font-family:'Cambria Math';font-size:11.0pt"><one:T><![CDATA[<!--[if mathML]><mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" display="block"><mml:msubsup><mml:mo stretchy="false">∫</mml:mo><mml:mn>0</mml:mn><mml:mn>1</mml:mn></mml:msubsup><mml:mrow><mml:mi>x</mml:mi><mml:mo>&nbsp;</mml:mo><mml:mi>d</mml:mi><mml:mi>x</mml:mi><mml:mo>&nbsp;</mml:mo></mml:mrow></mml:math><![endif]-->]]></one:T></one:OE><one:OE creationTime="2026-09-13T08:13:01.000Z" lastModifiedTime="2026-09-13T08:13:01.000Z" objectID="{8DBDED4C-17CA-0463-240E-91F1134B5E9F}{1}{B0}" alignment="left" style="font-family:'Cambria Math';font-size:11.0pt"><one:T><![CDATA[<!--[if mathML]><mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" display="block"><mml:munderover><mml:mo stretchy="false">∑</mml:mo><mml:mrow><mml:mi>i</mml:mi><mml:mo>=</mml:mo><mml:mn>1</mml:mn></mml:mrow><mml:mi>n</mml:mi></mml:munderover><mml:mrow><mml:mi>i</mml:mi><mml:mo>&nbsp;</mml:mo></mml:mrow></mml:math><![endif]-->]]></one:T></one:OE><one:OE creationTime="2026-09-13T08:13:01.000Z" lastModifiedTime="2026-09-13T08:13:01.000Z" objectID="{214F8EE1-64A0-0957-0A3F-F8E7D05B6586}{1}{B0}" alignment="left" style="font-family:'Cambria Math';font-size:11.0pt"><one:T><![CDATA[<!--[if mathML]><mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" display="block"><mml:msup><mml:mi>x</mml:mi><mml:mn>2</mml:mn></mml:msup><mml:mo>+</mml:mo><mml:mn>1</mml:mn></mml:math><![endif]-->]]></one:T></one:OE><one:OE creationTime="2026-09-13T08:13:01.000Z" lastModifiedTime="2026-09-13T08:13:01.000Z" objectID="{3715C26F-37BB-031C-1197-F79A9E19EE60}{1}{B0}" alignment="left" style="font-family:'Cambria Math';font-size:11.0pt"><one:T><![CDATA[<!--[if mathML]><mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" display="block"><mml:mfrac><mml:mrow><mml:mi>a</mml:mi><mml:mo>+</mml:mo><mml:mi>b</mml:mi></mml:mrow><mml:mrow><mml:msup><mml:mi>c</mml:mi><mml:mn>2</mml:mn></mml:msup></mml:mrow></mml:mfrac></mml:math><![endif]-->]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> | 3 | AAAAAElFTkSuQmCC |
| 4 | </one:Data></one:Image></one:OE><one:OE creationTime="2026-09-26T06:33:49.000Z" lastModifiedTime="2026-09-26T06:33:49.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{84}{B0}" alignment="left" style="font-family:Calibri;font-size:11.0pt"><one:T><![CDATA[After the picture]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/page-copy/cold/read/page-010.navigation.json+1-1| ... | @@ -6,7 +6,7 @@ | ... | @@ -6,7 +6,7 @@ |
| 6 | "h": 600, | 6 | "h": 600, |
| 7 | "error": null, | 7 | "error": null, |
| 8 | "win": { | 8 | "win": { |
| 9 | "title": "Move numbered subtree down - Microsoft OneNote", | 9 | "title": "Delete numbered subtree - Microsoft OneNote", |
| 10 | "class": "Framework::CFrame", | 10 | "class": "Framework::CFrame", |
| 11 | "dialog": false | 11 | "dialog": false |
| 12 | } | 12 | } |
corpus/page-copy/cold/read/page-010.png| Binary files a/corpus/page-copy/cold/read/page-010.png and b/corpus/page-copy/cold/read/page-010.png differ | |||
corpus/page-copy/cold/read/page-010.xml+3-3| ... | @@ -1,5 +1,5 @@ | ... | @@ -1,5 +1,5 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{79EFBB50-C028-09DE-005B-75E21D0ED54C}{1}{B0}" name="Move numbered subtree down" dateTime="2026-09-13T08:12:56.000Z" lastModifiedTime="2026-09-13T08:14:27.000Z" pageLevel="1" lang="en-US"><one:TagDef index="0" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Preserved task"/><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:QuickStyleDef index="1" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Copier" lastModifiedBy="Copier" creationTime="2026-09-13T08:12:56.000Z" lastModifiedTime="2026-09-13T08:12:56.000Z" objectID="{79EFBB50-C028-09DE-005B-75E21D0ED54C}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Move numbered subtree down]]></one:T></one:OE></one:Title><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-13T08:12:57.000Z" objectID="{0A534E63-2DFE-07C6-0A28-71334DEE9E4D}{1}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="142.6107788085937" height="67.13858032226562"/><one:OEChildren><one:OE creationTime="2026-09-13T08:12:57.000Z" lastModifiedTime="2026-09-13T08:12:57.000Z" objectID="{ED619F07-F6A1-0AC3-1461-04C34315FB37}{1}{B0}" alignment="left" quickStyleIndex="1"><one:List><one:Number numberSequence="1" numberFormat="##." fontSize="11.0" font="Calibri" text="I."/></one:List><one:T><![CDATA[Anchor]]></one:T></one:OE><one:OE creationTime="2026-09-13T08:12:57.000Z" lastModifiedTime="2026-09-13T08:12:57.000Z" objectID="{055B6314-3B1F-00E1-2734-2684ACC53EA4}{1}{B0}" alignment="left" quickStyleIndex="1"><one:Tag index="0" completed="true" disabled="false" creationDate="2020-01-02T03:04:05.000Z" completionDate="2020-01-02T04:05:06.000Z"/><one:List><one:Number numberSequence="1" numberFormat="##." fontSize="11.0" font="Calibri" text="II."/></one:List><one:T><![CDATA[<span | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{92FCCBE6-488E-0D05-1C9E-91FA438C6A79}{1}{B0}" name="Delete numbered subtree" dateTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:50:56.000Z" pageLevel="1" lang="en-US"><one:TagDef index="0" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Preserved task"/><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:QuickStyleDef index="1" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Copier" lastModifiedBy="Copier" creationTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{92FCCBE6-488E-0D05-1C9E-91FA438C6A79}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Delete numbered subtree]]></one:T></one:OE></one:Title><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-26T06:33:49.000Z" objectID="{5778544D-D680-0FAA-328B-B80D7F87C704}{155}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="142.6107788085937" height="67.13858032226562"/><one:OEChildren><one:OE creationTime="2026-09-26T06:33:49.000Z" lastModifiedTime="2026-09-26T06:33:49.000Z" objectID="{5778544D-D680-0FAA-328B-B80D7F87C704}{157}{B0}" alignment="left" quickStyleIndex="1"><one:List><one:Number numberSequence="1" numberFormat="##." fontSize="11.0" font="Calibri" text="I."/></one:List><one:T><![CDATA[Anchor]]></one:T></one:OE><one:OE creationTime="2026-09-26T06:33:49.000Z" lastModifiedTime="2026-09-26T06:33:49.000Z" objectID="{5778544D-D680-0FAA-328B-B80D7F87C704}{159}{B0}" alignment="left" quickStyleIndex="1"><one:Tag index="0" completed="true" disabled="false" creationDate="2020-01-02T03:04:05.000Z" completionDate="2020-01-02T04:05:06.000Z"/><one:List><one:Number numberSequence="1" numberFormat="##." fontSize="11.0" font="Calibri" text="II."/></one:List><one:T><![CDATA[<span |
| 3 | style='font-weight:bold'>Target &#129408; é</span> <a | 3 | style='font-weight:bold'>Target &#129408; é</span> <a |
| 4 | href="https://example.invalid/tree">Link label</a>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-13T08:12:57.000Z" lastModifiedTime="2026-09-13T08:12:57.000Z" objectID="{BC5317C5-A40F-01F9-038E-5AED4EB7626B}{1}{B0}" alignment="left" quickStyleIndex="1"><one:T style="font-family:Calibri;font-size:11.0pt"><![CDATA[<span | 4 | href="https://example.invalid/tree">Link label</a>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-26T06:33:49.000Z" lastModifiedTime="2026-09-26T06:33:49.000Z" objectID="{5778544D-D680-0FAA-328B-B80D7F87C704}{161}{B0}" alignment="left" quickStyleIndex="1"><one:T style="font-family:Calibri;font-size:11.0pt"><![CDATA[<span |
| 5 | style='font-style:italic'>Child</span>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-13T08:12:57.000Z" lastModifiedTime="2026-09-13T08:12:57.000Z" objectID="{7F1F3536-4683-03A0-2FCB-4FF372F15926}{1}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Grandchild]]></one:T></one:OE></one:OEChildren></one:OE></one:OEChildren></one:OE><one:OE creationTime="2026-09-13T08:12:57.000Z" lastModifiedTime="2026-09-13T08:12:57.000Z" objectID="{4ECF5731-6B02-0ABE-1485-8D4A0B859DBE}{1}{B0}" alignment="left" quickStyleIndex="1"><one:List><one:Number numberSequence="1" numberFormat="##." fontSize="11.0" font="Calibri" text="III."/></one:List><one:T><![CDATA[Trailing sibling]]></one:T></one:OE></one:OEChildren></one:Outline><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-13T08:12:57.000Z" objectID="{4A7372E3-CEB5-09D5-18D7-2A37D082BE91}{1}{B0}"><one:Position x="540.0" y="108.0" z="1"/><one:Size width="99.85796356201171" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-13T08:12:57.000Z" lastModifiedTime="2026-09-13T08:12:57.000Z" objectID="{DB6D2123-9BB9-016D-3D56-77B46F2BEE20}{1}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Other outline]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> | 5 | style='font-style:italic'>Child</span>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-26T06:33:49.000Z" lastModifiedTime="2026-09-26T06:33:49.000Z" objectID="{5778544D-D680-0FAA-328B-B80D7F87C704}{163}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Grandchild]]></one:T></one:OE></one:OEChildren></one:OE></one:OEChildren></one:OE><one:OE creationTime="2026-09-26T06:33:49.000Z" lastModifiedTime="2026-09-26T06:33:49.000Z" objectID="{5778544D-D680-0FAA-328B-B80D7F87C704}{165}{B0}" alignment="left" quickStyleIndex="1"><one:List><one:Number numberSequence="1" numberFormat="##." fontSize="11.0" font="Calibri" text="III."/></one:List><one:T><![CDATA[Trailing sibling]]></one:T></one:OE></one:OEChildren></one:Outline><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-26T06:33:49.000Z" objectID="{5778544D-D680-0FAA-328B-B80D7F87C704}{166}{B0}"><one:Position x="540.0" y="108.0" z="1"/><one:Size width="99.85796356201171" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-26T06:33:49.000Z" lastModifiedTime="2026-09-26T06:33:49.000Z" objectID="{5778544D-D680-0FAA-328B-B80D7F87C704}{168}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Other outline]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/page-copy/cold/read/page-011.navigation.json+1-1| ... | @@ -6,7 +6,7 @@ | ... | @@ -6,7 +6,7 @@ |
| 6 | "h": 600, | 6 | "h": 600, |
| 7 | "error": null, | 7 | "error": null, |
| 8 | "win": { | 8 | "win": { |
| 9 | "title": "Open task - Microsoft OneNote", | 9 | "title": "Bullet item - Microsoft OneNote", |
| 10 | "class": "Framework::CFrame", | 10 | "class": "Framework::CFrame", |
| 11 | "dialog": false | 11 | "dialog": false |
| 12 | } | 12 | } |
corpus/page-copy/cold/read/page-011.png| Binary files a/corpus/page-copy/cold/read/page-011.png and b/corpus/page-copy/cold/read/page-011.png differ | |||
corpus/page-copy/cold/read/page-011.xml+1-1| ... | @@ -1,2 +1,2 @@ | ... | @@ -1,2 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{8E5634C2-26CB-0458-2810-125D129037C8}{1}{B0}" name="Open task" dateTime="2026-09-13T08:12:59.000Z" lastModifiedTime="2026-09-13T08:14:27.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:TagDef index="0" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Rust task"/><one:TagDef index="1" type="1" symbol="13" fontColor="#FF0000" highlightColor="#FFFF00" name="Important"/><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Copier" lastModifiedBy="Copier" creationTime="2026-09-13T08:12:59.000Z" lastModifiedTime="2026-09-13T08:12:59.000Z" objectID="{8E5634C2-26CB-0458-2810-125D129037C8}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Open task]]></one:T></one:OE></one:Title><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-13T08:13:00.000Z" objectID="{0F36D11A-3FE8-0019-2E63-9AD912439D52}{1}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="158.2036895751953" height="53.71086883544922"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-13T08:13:00.000Z" lastModifiedTime="2026-09-13T08:13:00.000Z" objectID="{89DFA126-B932-01B1-1C58-55D810AA0133}{1}{B0}" alignment="left"><one:Tag index="0" completed="false" disabled="false" creationDate="2020-01-02T03:04:05.000Z"/><one:T><![CDATA[Open task]]></one:T></one:OE><one:OE creationTime="2026-09-13T08:13:00.000Z" lastModifiedTime="2026-09-13T08:13:00.000Z" objectID="{AD861E4A-D898-031B-25E9-10EBF29DCD25}{1}{B0}" alignment="left"><one:Tag index="0" completed="true" disabled="false" creationDate="2020-01-02T03:04:05.000Z" completionDate="2020-01-02T04:05:06.000Z"/><one:T><![CDATA[Completed task]]></one:T></one:OE><one:OE creationTime="2026-09-13T08:13:00.000Z" lastModifiedTime="2026-09-13T08:13:00.000Z" objectID="{EF2ED991-5964-0515-2042-9727E177E093}{1}{B0}" alignment="left"><one:Tag index="0" completed="false" disabled="false" creationDate="2020-01-02T03:04:05.000Z"/><one:Tag index="1" completed="false" disabled="false" creationDate="2020-01-02T03:04:05.000Z"/><one:T><![CDATA[Important open task]]></one:T></one:OE><one:OE creationTime="2026-09-13T08:13:00.000Z" lastModifiedTime="2026-09-13T08:13:00.000Z" objectID="{C1E4EF5F-DDB5-09B3-355F-A6449BF88678}{1}{B0}" alignment="left"><one:T><![CDATA[Untagged]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{9A577992-FE92-07C3-0C41-250E4E607861}{1}{B0}" name="Bullet item" dateTime="2026-09-26T06:33:49.000Z" lastModifiedTime="2026-09-26T06:51:08.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Copier" lastModifiedBy="Copier" creationTime="2026-09-26T06:33:49.000Z" lastModifiedTime="2026-09-26T06:33:49.000Z" objectID="{9A577992-FE92-07C3-0C41-250E4E607861}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Bullet item]]></one:T></one:OE></one:Title><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-26T06:33:50.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{144}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="174.7353515625" height="93.99401092529296"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-26T06:33:50.000Z" lastModifiedTime="2026-09-26T06:33:50.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{146}{B0}" alignment="left"><one:List><one:Bullet bullet="3" fontSize="11.0"/></one:List><one:T><![CDATA[Bullet item]]></one:T></one:OE><one:OE creationTime="2026-09-26T06:33:50.000Z" lastModifiedTime="2026-09-26T06:33:50.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{148}{B0}" alignment="left"><one:List><one:Number numberSequence="1" numberFormat="##." fontSize="11.0" font="Calibri" text="I."/></one:List><one:T><![CDATA[First numbered]]></one:T></one:OE><one:OE creationTime="2026-09-26T06:33:50.000Z" lastModifiedTime="2026-09-26T06:33:50.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{150}{B0}" alignment="left"><one:List><one:Number numberSequence="1" numberFormat="##." fontSize="11.0" font="Calibri" text="II."/></one:List><one:T><![CDATA[Second numbered]]></one:T><one:OEChildren><one:OE creationTime="2026-09-26T06:33:50.000Z" lastModifiedTime="2026-09-26T06:33:50.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{152}{B0}" alignment="left"><one:List><one:Number numberSequence="1" numberFormat="##." fontSize="11.0" font="Calibri" text="I."/></one:List><one:T><![CDATA[Nested numbered]]></one:T></one:OE></one:OEChildren></one:OE><one:OE creationTime="2026-09-26T06:33:50.000Z" lastModifiedTime="2026-09-26T06:33:50.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{154}{B0}" alignment="left"><one:List><one:Number numberSequence="1" numberFormat="##." fontSize="11.0" font="Calibri" text="III."/></one:List><one:T><![CDATA[Restarted at three]]></one:T><one:OEChildren><one:OE creationTime="2026-09-26T06:33:50.000Z" lastModifiedTime="2026-09-26T06:33:50.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{156}{B0}" alignment="left"><one:T><![CDATA[Nested plain]]></one:T></one:OE></one:OEChildren></one:OE><one:OE creationTime="2026-09-26T06:33:50.000Z" lastModifiedTime="2026-09-26T06:33:50.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{158}{B0}" alignment="left"><one:T><![CDATA[Plain again]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/page-copy/cold/read/page-012.navigation.json+1-1| ... | @@ -6,7 +6,7 @@ | ... | @@ -6,7 +6,7 @@ |
| 6 | "h": 600, | 6 | "h": 600, |
| 7 | "error": null, | 7 | "error": null, |
| 8 | "win": { | 8 | "win": { |
| 9 | "title": "Move cell subtree down - Microsoft OneNote", | 9 | "title": "Indent first subtree - Microsoft OneNote", |
| 10 | "class": "Framework::CFrame", | 10 | "class": "Framework::CFrame", |
| 11 | "dialog": false | 11 | "dialog": false |
| 12 | } | 12 | } |
corpus/page-copy/cold/read/page-012.png| Binary files a/corpus/page-copy/cold/read/page-012.png and b/corpus/page-copy/cold/read/page-012.png differ | |||
corpus/page-copy/cold/read/page-012.xml+3-3| ... | @@ -1,5 +1,5 @@ | ... | @@ -1,5 +1,5 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{AA29BE5C-9249-0610-1036-E2735CEC0BB9}{1}{B0}" name="Move cell subtree down" dateTime="2026-09-13T08:12:58.000Z" lastModifiedTime="2026-09-13T08:14:27.000Z" pageLevel="1" lang="en-US"><one:TagDef index="0" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Preserved task"/><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:QuickStyleDef index="1" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Copier" lastModifiedBy="Copier" creationTime="2026-09-13T08:12:58.000Z" lastModifiedTime="2026-09-13T08:12:58.000Z" objectID="{AA29BE5C-9249-0610-1036-E2735CEC0BB9}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Move cell subtree down]]></one:T></one:OE></one:Title><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-13T08:12:59.000Z" objectID="{5DFCE2F7-804C-019E-3F4A-DD0FF39FFE62}{1}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="428.1300048828125" height="62.23086929321289"/><one:OEChildren><one:OE creationTime="2026-09-13T08:12:58.000Z" lastModifiedTime="2026-09-13T08:12:59.000Z" objectID="{2A47635E-2030-0AED-007C-5D9D0EE60320}{1}{B0}" alignment="left"><one:Table bordersVisible="true" lastModifiedTime="2026-09-13T08:12:59.000Z" objectID="{132A6189-E6A3-06C8-2C3B-1301E8466F92}{1}{B0}"><one:Columns><one:Column index="0" width="240.0" isLocked="true"/><one:Column index="1" width="180.0" isLocked="true"/></one:Columns><one:Row objectID="{FFF81FBB-C1B7-0BED-2BAB-5A466882083D}{1}{B0}" lastModifiedTime="2026-09-13T08:12:59.000Z"><one:Cell lastModifiedTime="2026-09-13T08:12:59.000Z" objectID="{DE630286-2FC5-0B54-347C-FF399A93BA12}{1}{B0}"><one:OEChildren><one:OE creationTime="2026-09-13T08:12:58.000Z" lastModifiedTime="2026-09-13T08:12:59.000Z" objectID="{4B5A9D99-8F4C-0DE8-0E0A-5A618E02264F}{1}{B0}" alignment="left" quickStyleIndex="1"><one:Tag index="0" completed="true" disabled="false" creationDate="2020-01-02T03:04:05.000Z" completionDate="2020-01-02T04:05:06.000Z"/><one:T><![CDATA[<span | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{BFC636CB-B7BE-022A-1F7C-066F34B2A754}{1}{B0}" name="Indent first subtree" dateTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:50:55.000Z" pageLevel="1" lang="en-US"><one:TagDef index="0" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Preserved task"/><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:QuickStyleDef index="1" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Copier" lastModifiedBy="Copier" creationTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{BFC636CB-B7BE-022A-1F7C-066F34B2A754}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Indent first subtree]]></one:T></one:OE></one:Title><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{96}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="142.6107788085937" height="53.71086883544922"/><one:OEChildren><one:OE creationTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{98}{B0}" alignment="left" quickStyleIndex="1"><one:Tag index="0" completed="true" disabled="false" creationDate="2020-01-02T03:04:05.000Z" completionDate="2020-01-02T04:05:06.000Z"/><one:T><![CDATA[<span |
| 3 | style='font-weight:bold'>Target &#129408; é</span> <a | 3 | style='font-weight:bold'>Target &#129408; é</span> <a |
| 4 | href="https://example.invalid/tree">Link label</a>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-13T08:12:58.000Z" lastModifiedTime="2026-09-13T08:12:59.000Z" objectID="{27BE0601-EB2D-059C-24C6-205D71642566}{1}{B0}" alignment="left" quickStyleIndex="1"><one:T style="font-family:Calibri;font-size:11.0pt"><![CDATA[<span | 4 | href="https://example.invalid/tree">Link label</a>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{100}{B0}" alignment="left" quickStyleIndex="1"><one:T style="font-family:Calibri;font-size:11.0pt"><![CDATA[<span |
| 5 | style='font-style:italic'>Child</span>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-13T08:12:58.000Z" lastModifiedTime="2026-09-13T08:12:58.000Z" objectID="{E96A9E1E-A5AB-0668-1244-4836285586EF}{1}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Grandchild]]></one:T></one:OE></one:OEChildren></one:OE></one:OEChildren></one:OE><one:OE creationTime="2026-09-13T08:12:58.000Z" lastModifiedTime="2026-09-13T08:12:58.000Z" objectID="{6E58F561-F9E2-085E-1864-3AEDF1A5E9F7}{1}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Cell tail]]></one:T></one:OE></one:OEChildren></one:Cell><one:Cell lastModifiedTime="2026-09-13T08:12:58.000Z" objectID="{703BD781-4848-05D8-0381-00C40C756723}{1}{B0}"><one:OEChildren><one:OE creationTime="2026-09-13T08:12:58.000Z" lastModifiedTime="2026-09-13T08:12:58.000Z" objectID="{3A828837-47ED-0922-39C9-7A291414EFE2}{1}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Other cell]]></one:T></one:OE></one:OEChildren></one:Cell></one:Row></one:Table></one:OE></one:OEChildren></one:Outline><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-13T08:12:58.000Z" objectID="{68C4CF29-651E-0F55-2987-5FF0DC3E0D05}{1}{B0}"><one:Position x="540.0" y="108.0" z="1"/><one:Size width="99.85796356201171" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-13T08:12:58.000Z" lastModifiedTime="2026-09-13T08:12:58.000Z" objectID="{901AFAAD-146E-0CB5-1C0A-135DFC01AF28}{1}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Other outline]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> | 5 | style='font-style:italic'>Child</span>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{102}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Grandchild]]></one:T></one:OE></one:OEChildren></one:OE></one:OEChildren></one:OE><one:OE creationTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{104}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Trailing sibling]]></one:T></one:OE></one:OEChildren></one:Outline><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{105}{B0}"><one:Position x="540.0" y="108.0" z="1"/><one:Size width="99.85796356201171" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{107}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Other outline]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/page-copy/cold/read/page-013.navigation.json+1-1| ... | @@ -6,7 +6,7 @@ | ... | @@ -6,7 +6,7 @@ |
| 6 | "h": 600, | 6 | "h": 600, |
| 7 | "error": null, | 7 | "error": null, |
| 8 | "win": { | 8 | "win": { |
| 9 | "title": "Delete cell subtree - Microsoft OneNote", | 9 | "title": "Beside the drawing - Microsoft OneNote", |
| 10 | "class": "Framework::CFrame", | 10 | "class": "Framework::CFrame", |
| 11 | "dialog": false | 11 | "dialog": false |
| 12 | } | 12 | } |
corpus/page-copy/cold/read/page-013.png| Binary files a/corpus/page-copy/cold/read/page-013.png and b/corpus/page-copy/cold/read/page-013.png differ | |||
corpus/page-copy/cold/read/page-013.xml+6-4| ... | @@ -1,5 +1,7 @@ | ... | @@ -1,5 +1,7 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{BA810F75-DBC0-0827-0990-F9FBFC84B7D7}{1}{B0}" name="Delete cell subtree" dateTime="2026-09-13T08:12:56.000Z" lastModifiedTime="2026-09-13T08:14:27.000Z" pageLevel="1" lang="en-US"><one:TagDef index="0" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Preserved task"/><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:QuickStyleDef index="1" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Copier" lastModifiedBy="Copier" creationTime="2026-09-13T08:12:56.000Z" lastModifiedTime="2026-09-13T08:12:56.000Z" objectID="{BA810F75-DBC0-0827-0990-F9FBFC84B7D7}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Delete cell subtree]]></one:T></one:OE></one:Title><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-13T08:12:56.000Z" objectID="{EF2F80B0-92D7-0C40-3469-9AB6923AC934}{1}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="428.1300048828125" height="62.30315780639648"/><one:OEChildren><one:OE creationTime="2026-09-13T08:12:56.000Z" lastModifiedTime="2026-09-13T08:12:56.000Z" objectID="{A3D5DD07-CAAA-0FC1-3434-2BFCD5EAFB3A}{1}{B0}" alignment="left"><one:Table bordersVisible="true" lastModifiedTime="2026-09-13T08:12:56.000Z" objectID="{1C90E39E-CEB9-072B-1045-5350DE91C9BA}{1}{B0}"><one:Columns><one:Column index="0" width="240.0" isLocked="true"/><one:Column index="1" width="180.0" isLocked="true"/></one:Columns><one:Row objectID="{00FE4CB7-3AD9-0A13-02EC-F45825DA0837}{1}{B0}" lastModifiedTime="2026-09-13T08:12:56.000Z"><one:Cell lastModifiedTime="2026-09-13T08:12:56.000Z" objectID="{C0215DC2-A310-09E1-2C56-611011EEDEDA}{1}{B0}"><one:OEChildren><one:OE creationTime="2026-09-13T08:12:56.000Z" lastModifiedTime="2026-09-13T08:12:56.000Z" objectID="{B168AA68-D0D0-0783-3211-7CBCF62C1878}{1}{B0}" alignment="left" quickStyleIndex="1"><one:Tag index="0" completed="true" disabled="false" creationDate="2020-01-02T03:04:05.000Z" completionDate="2020-01-02T04:05:06.000Z"/><one:T><![CDATA[<span | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{CCD62F34-CB06-0E93-2181-938C6C097557}{1}{B0}" name="Beside the drawing" dateTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:50:56.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Copier" lastModifiedBy="Copier" creationTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{CCD62F34-CB06-0E93-2181-938C6C097557}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Beside the drawing]]></one:T></one:OE></one:Title><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-26T06:33:49.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{91}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="152.4244079589843" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-26T06:33:49.000Z" lastModifiedTime="2026-09-26T06:33:49.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{93}{B0}" alignment="left"><one:T><![CDATA[Beside the drawing]]></one:T></one:OE></one:OEChildren></one:Outline><one:InkDrawing lastModifiedTime="2026-09-26T06:33:49.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{94}{B0}"><one:Position x="299.9905395507812" y="119.9905471801758" z="1"/><one:Size width="60.03781127929687" height="60.03779220581055"/><one:Data>AM8CHQSiAaIBAWAqABofi0JwRYR1gL7QRdkE+oeVMvLsF0G4wBa/8yv0et67E4vlXrNInJuhJVq3 |
| 3 | style='font-weight:bold'>Target &#129408; é</span> <a | 3 | URK/7HHMzCKxQYkOuxMs7nqhWM9UiuaXxU+PBvi60uGbIsRmbacNslhJmh1OQDQGbBACEQZIEEUj |
| 4 | href="https://example.invalid/tree">Link label</a>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-13T08:12:56.000Z" lastModifiedTime="2026-09-13T08:12:56.000Z" objectID="{51E78125-5D88-02B2-1F60-3564BF9AB799}{1}{B0}" alignment="left" quickStyleIndex="1"><one:T style="font-family:Calibri;font-size:11.0pt"><![CDATA[<span | 4 | RiMJSBBE/wFFI0YjBQgLZU9mZ2hkaRkUMggAgB4CIeLiQTMIAPAQAovF4kFkAxVGAEAKXAWH8gln |
| 5 | style='font-style:italic'>Child</span>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-13T08:12:56.000Z" lastModifiedTime="2026-09-13T08:12:56.000Z" objectID="{9EE1315E-F0F0-01F2-236C-CED603CE6C83}{1}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Grandchild]]></one:T></one:OE></one:OEChildren></one:OE></one:OEChildren></one:OE><one:OE creationTime="2026-09-13T08:12:56.000Z" lastModifiedTime="2026-09-13T08:12:56.000Z" objectID="{FD8B95A9-B069-065B-24ED-358E77DA9126}{1}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Cell tail]]></one:T></one:OE></one:OEChildren></one:Cell><one:Cell lastModifiedTime="2026-09-13T08:12:56.000Z" objectID="{C371CC3D-4164-0E42-20D2-92CDC0E0162E}{1}{B0}"><one:OEChildren><one:OE creationTime="2026-09-13T08:12:56.000Z" lastModifiedTime="2026-09-13T08:12:56.000Z" objectID="{3A7C1016-52CD-0EE0-1D98-5D6464F47C40}{1}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Other cell]]></one:T></one:OE><one:OE creationTime="2026-09-13T08:12:56.000Z" lastModifiedTime="2026-09-13T08:12:56.000Z" objectID="{D4CC12A4-5B3C-0061-0FC8-329CFD2237A8}{1}{B0}" alignment="left" style="font-family:Calibri;font-size:11.0pt"><one:Table bordersVisible="true" lastModifiedTime="2026-09-13T08:12:56.000Z" objectID="{2BDFCA04-550A-0B11-3229-C359C02CEC1A}{1}{B0}"><one:Columns><one:Column index="0" width="72.0" isLocked="true"/></one:Columns><one:Row objectID="{933BCC33-B101-006B-1B06-D1EA10BD608E}{1}{B0}" lastModifiedTime="2026-09-13T08:12:56.000Z"><one:Cell lastModifiedTime="2026-09-13T08:12:56.000Z" objectID="{C9AC97DE-B8A6-079A-05EA-1C006AE7F1F6}{1}{B0}"><one:OEChildren><one:OE creationTime="2026-09-13T08:12:56.000Z" lastModifiedTime="2026-09-13T08:12:56.000Z" objectID="{F3B663D9-B871-0F4A-0480-14D1C093E86C}{1}{B0}" alignment="left"><one:T><![CDATA[Inner one]]></one:T></one:OE></one:OEChildren></one:Cell></one:Row><one:Row objectID="{49CDA646-4B5C-05D4-1D4A-377146640436}{1}{B0}" lastModifiedTime="2026-09-13T08:12:56.000Z"><one:Cell lastModifiedTime="2026-09-13T08:12:56.000Z" objectID="{4C20D881-145C-0B85-3B52-D9FC9DA41CF8}{1}{B0}"><one:OEChildren><one:OE creationTime="2026-09-13T08:12:56.000Z" lastModifiedTime="2026-09-13T08:12:56.000Z" objectID="{23ACBCB6-41CC-0F28-29A2-649DCD1F070A}{1}{B0}" alignment="left"><one:T><![CDATA[Inner two]]></one:T></one:OE></one:OEChildren></one:Cell></one:Row></one:Table></one:OE></one:OEChildren></one:Cell></one:Row></one:Table></one:OE></one:OEChildren></one:Outline><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-13T08:12:56.000Z" objectID="{DD272184-6150-017E-0C7B-E0A6C9D33F67}{1}{B0}"><one:Position x="540.0" y="108.0" z="1"/><one:Size width="99.85796356201171" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-13T08:12:56.000Z" lastModifiedTime="2026-09-13T08:12:56.000Z" objectID="{E23E61E5-7407-0548-006A-201CB1623B82}{1}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Other outline]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> | 5 | jCj7wnvCe8IAh/B8h4Pke8I7wnvCQAUURgBAAAAJBAUURgAAAAASABEgZQaCZUd9r0ufE8XliyT+ |
| 6 | gAoAESCARDH9gE3dAQMVRgBACj9AIm9GCC70WAqACQEKTAIPUq7GcA5CJYzgBRRGAIAAAAkEBRRG | ||
| 7 | AAAAABIAESBryQ6p9QoiSLYaYOAFf0kKCgARIIBEMf2ATd0BAxVGAEAKP0Aib0YILvRYCoA=</one:Data></one:InkDrawing></one:Page> |
corpus/page-copy/cold/read/page-014.navigation.json+1-1| ... | @@ -6,7 +6,7 @@ | ... | @@ -6,7 +6,7 @@ |
| 6 | "h": 600, | 6 | "h": 600, |
| 7 | "error": null, | 7 | "error": null, |
| 8 | "win": { | 8 | "win": { |
| 9 | "title": "Beside the drawing - Microsoft OneNote", | 9 | "title": "A notebook created in Rust. - Microsoft OneNote", |
| 10 | "class": "Framework::CFrame", | 10 | "class": "Framework::CFrame", |
| 11 | "dialog": false | 11 | "dialog": false |
| 12 | } | 12 | } |
corpus/page-copy/cold/read/page-014.png| Binary files a/corpus/page-copy/cold/read/page-014.png and b/corpus/page-copy/cold/read/page-014.png differ | |||
corpus/page-copy/cold/read/page-014.xml+1-6| ... | @@ -1,7 +1,2 @@ | ... | @@ -1,7 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{D28E7576-C55F-0047-2C51-4C855C536D13}{1}{B0}" name="Beside the drawing" dateTime="2026-09-13T08:12:58.000Z" lastModifiedTime="2026-09-13T08:14:27.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Copier" lastModifiedBy="Copier" creationTime="2026-09-13T08:12:58.000Z" lastModifiedTime="2026-09-13T08:12:58.000Z" objectID="{D28E7576-C55F-0047-2C51-4C855C536D13}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Beside the drawing]]></one:T></one:OE></one:Title><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-13T08:12:59.000Z" objectID="{110E90F1-B6EC-0EE0-1F13-367858D96194}{1}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="152.4244079589843" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-13T08:12:59.000Z" lastModifiedTime="2026-09-13T08:12:59.000Z" objectID="{14A2DDB9-EF56-053E-2D5F-B9FF92056636}{1}{B0}" alignment="left"><one:T><![CDATA[Beside the drawing]]></one:T></one:OE></one:OEChildren></one:Outline><one:InkDrawing lastModifiedTime="2026-09-13T08:12:59.000Z" objectID="{A013EA2B-3C74-0FE2-0325-A02BD639D5F9}{1}{B0}"><one:Position x="299.9905395507812" y="119.9905471801758" z="1"/><one:Size width="60.03781127929687" height="60.03779220581055"/><one:Data>AM8CHQSiAaIBAWAqABofi0JwRYR1gL7QRdkE+oeVMvLsF0G4wBa/8yv0et67E4vlXrNInJuhJVq3 | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{CF7197EA-123A-02D7-12CE-E9F5A3788534}{1}{B0}" name="A notebook created in Rust." dateTime="2026-09-26T06:33:49.000Z" lastModifiedTime="2026-09-26T06:50:56.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Copier" lastModifiedBy="Copier" creationTime="2026-09-26T06:33:49.000Z" lastModifiedTime="2026-09-26T06:33:49.000Z" objectID="{CF7197EA-123A-02D7-12CE-E9F5A3788534}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[A notebook created in Rust.]]></one:T></one:OE></one:Title><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-26T06:33:50.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{208}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="191.1661224365234" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-26T06:33:50.000Z" lastModifiedTime="2026-09-26T06:33:50.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{210}{B0}" alignment="left"><one:T><![CDATA[A notebook created in Rust.]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
| 3 | URK/7HHMzCKxQYkOuxMs7nqhWM9UiuaXxU+PBvi60uGbIsRmbacNslhJmh1OQDQGbBACEQZIEEUj | ||
| 4 | RiMJSBBE/wFFI0YjBQgLZU9mZ2hkaRkUMggAgB4CIeLiQTMIAPAQAovF4kFkAxVGAEAKXAWH8gln | ||
| 5 | jCj7wnvCe8IAh/B8h4Pke8I7wnvCQAUURgBAAAAJBAUURgAAAAASABEgNO2fBK2iXEeJsDGBngJF | ||
| 6 | LQoAESCAd0ywV0PdAQMVRgBACj9AIm9GCC70WAqACQEKTAIPUq7GcA5CJYzgBRRGAIAAAAkEBRRG | ||
| 7 | AAAAABIAESDm2ofXdOgLSJUWT/x4VvZtCgARIIB3TLBXQ90BAxVGAEAKP0Aib0YILvRYCoA=</one:Data></one:InkDrawing></one:Page> |
corpus/page-copy/cold/read/page-015.navigation.json+1-1| ... | @@ -6,7 +6,7 @@ | ... | @@ -6,7 +6,7 @@ |
| 6 | "h": 600, | 6 | "h": 600, |
| 7 | "error": null, | 7 | "error": null, |
| 8 | "win": { | 8 | "win": { |
| 9 | "title": "A notebook created in Rust. - Microsoft OneNote", | 9 | "title": "Open task - Microsoft OneNote", |
| 10 | "class": "Framework::CFrame", | 10 | "class": "Framework::CFrame", |
| 11 | "dialog": false | 11 | "dialog": false |
| 12 | } | 12 | } |
corpus/page-copy/cold/read/page-015.png| Binary files a/corpus/page-copy/cold/read/page-015.png and b/corpus/page-copy/cold/read/page-015.png differ | |||
corpus/page-copy/cold/read/page-015.xml+1-1| ... | @@ -1,2 +1,2 @@ | ... | @@ -1,2 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{EF547C09-87EA-0A35-039B-F5512549E931}{1}{B0}" name="A notebook created in Rust." dateTime="2026-09-13T08:13:00.000Z" lastModifiedTime="2026-09-13T08:14:27.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Copier" lastModifiedBy="Copier" creationTime="2026-09-13T08:13:00.000Z" lastModifiedTime="2026-09-13T08:13:00.000Z" objectID="{EF547C09-87EA-0A35-039B-F5512549E931}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[A notebook created in Rust.]]></one:T></one:OE></one:Title><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-13T08:13:01.000Z" objectID="{B7774BCB-9107-00A0-2BFE-95DC3CB2134F}{1}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="191.1661224365234" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-13T08:13:01.000Z" lastModifiedTime="2026-09-13T08:13:01.000Z" objectID="{C3848036-370C-04C5-0022-5090B87608DD}{1}{B0}" alignment="left"><one:T><![CDATA[A notebook created in Rust.]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{DE01F667-2BDD-03D0-14A4-27E9A5F8CC0C}{1}{B0}" name="Open task" dateTime="2026-09-26T06:33:49.000Z" lastModifiedTime="2026-09-26T06:50:56.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:TagDef index="0" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Rust task"/><one:TagDef index="1" type="1" symbol="13" fontColor="#FF0000" highlightColor="#FFFF00" name="Important"/><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Copier" lastModifiedBy="Copier" creationTime="2026-09-26T06:33:49.000Z" lastModifiedTime="2026-09-26T06:33:49.000Z" objectID="{DE01F667-2BDD-03D0-14A4-27E9A5F8CC0C}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Open task]]></one:T></one:OE></one:Title><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-26T06:33:50.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{110}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="158.2036895751953" height="53.71086883544922"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-26T06:33:50.000Z" lastModifiedTime="2026-09-26T06:33:50.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{112}{B0}" alignment="left"><one:Tag index="0" completed="false" disabled="false" creationDate="2020-01-02T03:04:05.000Z"/><one:T><![CDATA[Open task]]></one:T></one:OE><one:OE creationTime="2026-09-26T06:33:50.000Z" lastModifiedTime="2026-09-26T06:33:50.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{114}{B0}" alignment="left"><one:Tag index="0" completed="true" disabled="false" creationDate="2020-01-02T03:04:05.000Z" completionDate="2020-01-02T04:05:06.000Z"/><one:T><![CDATA[Completed task]]></one:T></one:OE><one:OE creationTime="2026-09-26T06:33:50.000Z" lastModifiedTime="2026-09-26T06:33:50.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{116}{B0}" alignment="left"><one:Tag index="0" completed="false" disabled="false" creationDate="2020-01-02T03:04:05.000Z"/><one:Tag index="1" completed="false" disabled="false" creationDate="2020-01-02T03:04:05.000Z"/><one:T><![CDATA[Important open task]]></one:T></one:OE><one:OE creationTime="2026-09-26T06:33:50.000Z" lastModifiedTime="2026-09-26T06:33:50.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{118}{B0}" alignment="left"><one:T><![CDATA[Untagged]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/page-copy/cold/read/page-016.navigation.json+1-1| ... | @@ -6,7 +6,7 @@ | ... | @@ -6,7 +6,7 @@ |
| 6 | "h": 600, | 6 | "h": 600, |
| 7 | "error": null, | 7 | "error": null, |
| 8 | "win": { | 8 | "win": { |
| 9 | "title": "Before the table - Microsoft OneNote", | 9 | "title": "Bold \ud83e\udd80 italic e\u0301 color \u6771\u4eac - Microsoft OneNote", |
| 10 | "class": "Framework::CFrame", | 10 | "class": "Framework::CFrame", |
| 11 | "dialog": false | 11 | "dialog": false |
| 12 | } | 12 | } |
corpus/page-copy/cold/read/page-016.png| Binary files a/corpus/page-copy/cold/read/page-016.png and b/corpus/page-copy/cold/read/page-016.png differ | |||
corpus/page-copy/cold/read/page-016.xml+19-1| ... | @@ -1,2 +1,20 @@ | ... | @@ -1,2 +1,20 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{FBBA4589-4114-0DBB-1DDE-9AEBE04435E8}{1}{B0}" name="Before the table" dateTime="2026-09-13T08:12:55.000Z" lastModifiedTime="2026-09-13T08:14:26.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Copier" lastModifiedBy="Copier" creationTime="2026-09-13T08:12:55.000Z" lastModifiedTime="2026-09-13T08:12:55.000Z" objectID="{FBBA4589-4114-0DBB-1DDE-9AEBE04435E8}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Before the table]]></one:T></one:OE></one:Title><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-13T08:12:55.000Z" objectID="{B66EC4E0-90E0-065D-16BD-8AE6D17D38E2}{1}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="395.1299438476562" height="67.21086883544922"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-13T08:12:55.000Z" lastModifiedTime="2026-09-13T08:12:55.000Z" objectID="{D00B3AB9-E98D-02AC-00A5-64F7F0B73D24}{1}{B0}" alignment="left"><one:T><![CDATA[Before the table]]></one:T></one:OE><one:OE creationTime="2026-09-13T08:12:55.000Z" lastModifiedTime="2026-09-13T08:12:55.000Z" objectID="{A910B5E4-106E-091B-042B-85260A415D85}{1}{B0}" alignment="left"><one:Table bordersVisible="true" lastModifiedTime="2026-09-13T08:12:55.000Z" objectID="{E2111011-156E-0E62-3045-C3CA9B0171B1}{1}{B0}"><one:Columns><one:Column index="0" width="144.0" isLocked="true"/><one:Column index="1" width="216.0" isLocked="true"/></one:Columns><one:Row objectID="{EFFCB8A1-87E0-0D1C-2007-873484353285}{1}{B0}" lastModifiedTime="2026-09-13T08:12:55.000Z"><one:Cell lastModifiedTime="2026-09-13T08:12:55.000Z" objectID="{58537E0F-3889-000A-30F5-5B63A4AEDDF4}{1}{B0}"><one:OEChildren><one:OE creationTime="2026-09-13T08:12:55.000Z" lastModifiedTime="2026-09-13T08:12:55.000Z" objectID="{766E8FDE-7B1E-0BE1-2EA2-786315A5EE3A}{1}{B0}" alignment="left"><one:T><![CDATA[Name]]></one:T></one:OE></one:OEChildren></one:Cell><one:Cell lastModifiedTime="2026-09-13T08:12:55.000Z" objectID="{33808FC6-C25F-040B-157F-8170CE036733}{1}{B0}"><one:OEChildren><one:OE creationTime="2026-09-13T08:12:55.000Z" lastModifiedTime="2026-09-13T08:12:55.000Z" objectID="{90D3178B-5FBE-03CD-3CC8-4D2F6E8B7FD3}{1}{B0}" alignment="left"><one:T><![CDATA[Value]]></one:T></one:OE></one:OEChildren></one:Cell></one:Row><one:Row objectID="{EA57AB8A-1061-0815-135C-39D97AF028C6}{1}{B0}" lastModifiedTime="2026-09-13T08:12:55.000Z"><one:Cell lastModifiedTime="2026-09-13T08:12:55.000Z" objectID="{92B2BEB0-1EE8-0D4A-1D27-79EC8DB2F9A2}{1}{B0}"><one:OEChildren><one:OE creationTime="2026-09-13T08:12:55.000Z" lastModifiedTime="2026-09-13T08:12:55.000Z" objectID="{DCEBCF8B-F7DA-0C20-0F76-44AC9B43F901}{1}{B0}" alignment="left"><one:T><![CDATA[Rust &#129408;]]></one:T></one:OE></one:OEChildren></one:Cell><one:Cell lastModifiedTime="2026-09-13T08:12:55.000Z" objectID="{71E65FA6-5919-0FAD-16C0-ADD201204FEE}{1}{B0}"><one:OEChildren><one:OE creationTime="2026-09-13T08:12:55.000Z" lastModifiedTime="2026-09-13T08:12:55.000Z" objectID="{A457D70F-1E86-09AC-0205-D8DFEA8EEC54}{1}{B0}" alignment="left"><one:T><![CDATA[Written natively]]></one:T></one:OE></one:OEChildren></one:Cell></one:Row></one:Table></one:OE><one:OE creationTime="2026-09-13T08:12:55.000Z" lastModifiedTime="2026-09-13T08:12:55.000Z" objectID="{534343F8-0718-0974-08DF-524AD8D8860E}{1}{B0}" alignment="left"><one:T><![CDATA[After the table]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{E0635CAD-AE50-05FE-22E9-82F2F75DB84D}{1}{B0}" name="Bold 🦀 italic é color 東京" dateTime="2026-09-26T06:33:47.000Z" lastModifiedTime="2026-09-26T06:50:55.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:QuickStyleDef index="1" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Copier" lastModifiedBy="Copier" creationTime="2026-09-26T06:33:47.000Z" lastModifiedTime="2026-09-26T06:33:47.000Z" objectID="{E0635CAD-AE50-05FE-22E9-82F2F75DB84D}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Bold &#129408; italic é color 東京]]></one:T></one:OE></one:Title><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{64}{B0}"><one:Position x="36.0" y="14.40000057220459" z="0"/><one:Size width="127.5136947631836" height="42.03070831298828"/><one:OEChildren><one:OE creationTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{66}{B0}" alignment="left"><one:T><![CDATA[<span style='font-weight:bold; |
| 3 | text-decoration:underline;font-family:Calibri;font-size:11.0pt'>Bold</span><span | ||
| 4 | style='font-family:Calibri;font-size:11.0pt'> </span><span style='font-family: | ||
| 5 | Arial;font-size:13.5pt'>&#129408;</span><span style='font-family:Calibri; | ||
| 6 | font-size:11.0pt'> </span><span style='font-style:italic;text-decoration:line-through; | ||
| 7 | font-family:Calibri;font-size:11.0pt'>italic</span><span style='font-family: | ||
| 8 | Calibri;font-size:11.0pt'> </span><span style='vertical-align:super;font-family: | ||
| 9 | Calibri;font-size:11.0pt'>é</span><span style='font-family:Calibri;font-size: | ||
| 10 | 11.0pt'> </span><span style='font-family:Calibri;font-size:11.0pt;color:#0C2238; | ||
| 11 | background:yellow;mso-highlight:yellow'>color</span><span style='font-family: | ||
| 12 | Calibri;font-size:11.0pt'> </span><span style='vertical-align:sub;font-family: | ||
| 13 | Calibri;font-size:11.0pt'>東京</span><span style='font-family:Calibri;font-size: | ||
| 14 | 11.0pt'><br> | ||
| 15 | End</span>]]></one:T></one:OE><one:OE creationTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{68}{B0}" alignment="left" quickStyleIndex="1" style="font-size:11.0pt;color:#1F4E79"><one:T><![CDATA[<span | ||
| 16 | style='font-weight:bold;font-family:Calibri' lang=en-US>Fictitious: café, </span><span | ||
| 17 | style='font-weight:bold;font-family:SimSun' lang=en-US>東京</span><span | ||
| 18 | style='font-weight:bold;font-family:Calibri' lang=en-US>, </span><span | ||
| 19 | style='font-weight:bold;font-family:Arial;direction:rtl;unicode-bidi:embed' | ||
| 20 | lang=ar-SA>مرحبا</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/page-copy/cold/read/payloads.json+2-2| ... | @@ -2,9 +2,9 @@ | ... | @@ -2,9 +2,9 @@ |
| 2 | { | 2 | { |
| 3 | "sha256": "b20a14e044e33eac39b4665e7d79813b8a3672dbbd27ed9d2e4ac866c13ec4b6", | 3 | "sha256": "b20a14e044e33eac39b4665e7d79813b8a3672dbbd27ed9d2e4ac866c13ec4b6", |
| 4 | "name": "notes 🦀.txt", | 4 | "name": "notes 🦀.txt", |
| 5 | "object": "{9E89195D-B7EC-0AC4-34FD-F47D633F0533}{1}{B0}", | 5 | "object": "{82855B32-C890-0FF0-3238-816FD0683ADC}{12}{B0}", |
| 6 | "kind": "InsertedFile", | 6 | "kind": "InsertedFile", |
| 7 | "page": "{62E12C49-D5FE-0850-17D5-B4DC95B736E9}{1}{B0}", | 7 | "page": "{0C0ECE53-81B9-0DE0-2721-D119E26F7632}{1}{B0}", |
| 8 | "bytes": 28 | 8 | "bytes": 28 |
| 9 | } | 9 | } |
| 10 | ] | 10 | ] |
| \ No newline at end of file | |||
corpus/page-copy/cold/run.json+1-1| ... | @@ -1,5 +1,5 @@ | ... | @@ -1,5 +1,5 @@ |
| 1 | { | 1 | { |
| 2 | "notebook": "/private/tmp/m4/copy", | 2 | "notebook": "/Users/clo/dev/one/evidence/titles/page-copy", |
| 3 | "expected_pages": 17, | 3 | "expected_pages": 17, |
| 4 | "author": null, | 4 | "author": null, |
| 5 | "author_timeout_seconds": 600, | 5 | "author_timeout_seconds": 600, |
corpus/page-copy/cold/source.json+6-6| ... | @@ -2,19 +2,19 @@ | ... | @@ -2,19 +2,19 @@ |
| 2 | { | 2 | { |
| 3 | "path": "Open Notebook.onetoc2", | 3 | "path": "Open Notebook.onetoc2", |
| 4 | "bytes": 1604, | 4 | "bytes": 1604, |
| 5 | "sha256": "acb0747c250f7bebed1e90b18d5dbbf8c32731f9287571b1e732681921fd323a", | 5 | "sha256": "61afe63a5058198015858383382d2776cecfae45b6afea1a81c7d379f8b78ca8", |
| 6 | "mtime_ns": 1789287183577857848 | 6 | "mtime_ns": 1790404431194548185 |
| 7 | }, | 7 | }, |
| 8 | { | 8 | { |
| 9 | "path": "copies.one", | 9 | "path": "copies.one", |
| 10 | "bytes": 185400, | 10 | "bytes": 163632, |
| 11 | "sha256": "dcd7c226d86477adc1ad2f8da8a408ffa2b993b372517ab8d464e5746a971aac", | 11 | "sha256": "75253d18459607aee385ceb73499d3780903e39fe436132666e842fd100b8d40", |
| 12 | "mtime_ns": 1789287183575141938 | 12 | "mtime_ns": 1790404431191555148 |
| 13 | }, | 13 | }, |
| 14 | { | 14 | { |
| 15 | "path": "expected-count.txt", | 15 | "path": "expected-count.txt", |
| 16 | "bytes": 2, | 16 | "bytes": 2, |
| 17 | "sha256": "4523540f1504cd17100c4835e85b7eefd49911580f8efff0599a8f283be6b9e3", | 17 | "sha256": "4523540f1504cd17100c4835e85b7eefd49911580f8efff0599a8f283be6b9e3", |
| 18 | "mtime_ns": 1789287183577930514 | 18 | "mtime_ns": 1790404431194611352 |
| 19 | } | 19 | } |
| 20 | ] | 20 | ] |
corpus/page-lifecycle/creation/README.md+2-1| ... | @@ -14,7 +14,8 @@ The retained `provenance.json` records the native capture inputs and clone remov | ... | @@ -14,7 +14,8 @@ The retained `provenance.json` records the native capture inputs and clone remov |
| 14 | After building workspace examples, run `python -m unittest discover -s tools | 14 | After building workspace examples, run `python -m unittest discover -s tools |
| 15 | -p test_page_creation.py -v`. It compares native XML content and character formats, | 15 | -p test_page_creation.py -v`. It compares native XML content and character formats, |
| 16 | ordered identities, and complete active object graphs across each cold reopen. | 16 | ordered identities, and complete active object graphs across each cold reopen. |
| 17 | These thirteen-page comparisons need no graph normalization. | 17 | Only `followup/cold` differs: OneNote refreshes the section's cached copies of the |
| 18 | two renamed titles, which the Rust title edit leaves stale. | ||
| 18 | 19 | ||
| 19 | Export fresh Rust candidates by setting each variable to a new output directory: | 20 | Export fresh Rust candidates by setting each variable to a new output directory: |
| 20 | 21 |
corpus/page-lifecycle/creation/candidate/Lifecycle.one| Binary files a/corpus/page-lifecycle/creation/candidate/Lifecycle.one and b/corpus/page-lifecycle/creation/candidate/Lifecycle.one differ | |||
corpus/page-lifecycle/creation/cold/notebook/Lifecycle.one| Binary files a/corpus/page-lifecycle/creation/cold/notebook/Lifecycle.one and b/corpus/page-lifecycle/creation/cold/notebook/Lifecycle.one differ | |||
corpus/page-lifecycle/creation/cold/read/hierarchy.xml+1-1| ... | @@ -1,2 +1,2 @@ | ... | @@ -1,2 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Notebook xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" name="notebook" nickname="notebook" ID="{3BFEF56D-B4F8-4F2E-A1BC-468DDA94A2D5}{1}{B0}" path="C:\one-tests\runs\capture\notebook" lastModifiedTime="2026-09-09T04:23:59.000Z" color="#B49EDE"><one:Section name="Lifecycle" ID="{C65593FF-0E35-0BF7-1FC3-16CBBB799594}{1}{B0}" path="C:\one-tests\runs\capture\notebook\Lifecycle.one" lastModifiedTime="2026-09-09T04:23:59.000Z" color="#91BAAE"><one:Page ID="{39C1FCAE-45F8-0FA4-3FC6-46AF3A75DC8A}{1}{B0}" name="Untitled page" dateTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:23:58.000Z" pageLevel="1"/><one:Page ID="{266FCA0A-41B5-03F0-0ADE-86136233B704}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-09T04:23:58.000Z" pageLevel="1"/><one:Page ID="{6C02B3F9-D11E-0A62-1E9D-86328FA37C2A}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-09T04:23:58.000Z" pageLevel="1"/><one:Page ID="{4C0D72CA-D8C6-0D40-0E00-40C67411544F}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-09T04:23:58.000Z" pageLevel="1"/><one:Page ID="{58C13BB3-DE03-4816-B303-087ACC456B97}{1}{B0}" name="Same title" dateTime="2026-09-09T03:09:27.000Z" lastModifiedTime="2026-09-09T04:23:58.000Z" pageLevel="1"/><one:Page ID="{054D1020-5845-4210-8DEE-30AE886FFF66}{1}{B0}" name="child" dateTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-09T04:23:58.000Z" pageLevel="1"/><one:Page ID="{5891D2E3-C28F-4C4F-91C8-E802A2DDA3B0}{1}{B0}" name="grandchild" dateTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-09T04:23:58.000Z" pageLevel="1"/><one:Page ID="{8B5220CE-1103-4F42-AD6F-443875AA4EFC}{1}{B0}" name="Renamed 🦋 é" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T04:23:58.000Z" pageLevel="1"/><one:Page ID="{E0B7DA6E-27FF-4573-92CF-4663E293DAE6}{1}{B0}" name="Body trailing 🦀 é preserved" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T04:23:59.000Z" pageLevel="1"/><one:Page ID="{9BFEBE8D-72F6-4211-B975-2E62E65AF7D7}{1}{B0}" name="Body automatic 🦀 é preserved" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T04:23:59.000Z" pageLevel="1"/><one:Page ID="{3894C714-5BE5-03FC-208C-EFB4E9A37951}{1}{B0}" name="Untitled page" dateTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:23:59.000Z" pageLevel="1"/><one:Page ID="{067A6101-C793-0F15-0101-74668CB91D5E}{1}{B0}" name="New 🦋 é" dateTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:23:59.000Z" pageLevel="1"/><one:Page ID="{C6BB9F1B-5105-064D-086F-0B103A30C43B}{1}{B0}" name="New 🦋 é" dateTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:23:59.000Z" pageLevel="1"/></one:Section></one:Notebook> | 2 | <one:Notebook xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" name="notebook" nickname="notebook" ID="{30D39FB3-2305-41E5-96E8-7E153D82228E}{1}{B0}" path="C:\one-tests\runs\capture\notebook" lastModifiedTime="2026-09-26T06:36:22.000Z" color="#B49EDE"><one:Section name="Lifecycle" ID="{E28A0F5E-BCA3-0FE8-0F78-76EF8E6F5E50}{1}{B0}" path="C:\one-tests\runs\capture\notebook\Lifecycle.one" lastModifiedTime="2026-09-26T06:36:22.000Z" color="#91BAAE"><one:Page ID="{677392C8-6B9B-099F-32D5-118DD212AA11}{1}{B0}" name="Untitled page" dateTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T06:36:21.000Z" pageLevel="1"/><one:Page ID="{02B056AB-F323-07EF-1A65-E63757257CC0}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-26T06:36:21.000Z" pageLevel="1"/><one:Page ID="{48DD2F58-6388-0E7D-0E26-E616BAB5B7EE}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-26T06:36:21.000Z" pageLevel="1"/><one:Page ID="{68D2EE6B-6A50-095F-1EBB-20E241079F8B}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-26T06:36:21.000Z" pageLevel="1"/><one:Page ID="{7C1EA712-6C95-4C09-A3B8-685EF953A053}{1}{B0}" name="Same title" dateTime="2026-09-09T03:09:27.000Z" lastModifiedTime="2026-09-26T06:36:21.000Z" pageLevel="1"/><one:Page ID="{21928C81-EAD3-460F-9D55-508ABD7934A2}{1}{B0}" name="child" dateTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-26T06:36:21.000Z" pageLevel="1"/><one:Page ID="{7C4E4E42-7019-4850-8173-882697CB6874}{1}{B0}" name="grandchild" dateTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-26T06:36:21.000Z" pageLevel="1"/><one:Page ID="{AF8DBC6F-A395-4B5D-BDD4-241C40BC8538}{1}{B0}" name="Renamed 🦋 é" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-26T06:36:21.000Z" pageLevel="1"/><one:Page ID="{C46846CF-9569-416C-8274-2647D7851122}{1}{B0}" name="Body trailing 🦀 é preserved" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-26T06:36:21.000Z" pageLevel="1"/><one:Page ID="{BF21222C-C060-460E-A9CE-4E46D34C3C13}{1}{B0}" name="Body automatic 🦀 é preserved" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-26T06:36:21.000Z" pageLevel="1"/><one:Page ID="{0F6FFE25-3632-0AD1-3AE1-41B9FD2DEB48}{1}{B0}" name="Untitled page" dateTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T06:36:22.000Z" pageLevel="1"/><one:Page ID="{CCABB477-9855-0E14-1DCB-06D00F5CFE0E}{1}{B0}" name="New 🦋 é" dateTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T06:36:22.000Z" pageLevel="1"/><one:Page ID="{82AE846C-D1FD-07B3-23E5-002BB0B8F1F6}{1}{B0}" name="New 🦋 é" dateTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T06:36:22.000Z" pageLevel="1"/></one:Section></one:Notebook> |
corpus/page-lifecycle/creation/cold/read/page-000.xml+1-3| ... | @@ -1,4 +1,2 @@ | ... | @@ -1,4 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{054D1020-5845-4210-8DEE-30AE886FFF66}{1}{B0}" name="child" dateTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-09T04:23:58.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-09T03:09:29.000Z" objectID="{8F196553-BA68-46B8-A5A9-2FFC33D426C2}{28}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[child]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{8F196553-BA68-46B8-A5A9-2FFC33D426C2}{35}{B0}"><one:Position x="36.0" y="86.4000015258789" z="0"/><one:Size width="149.6102294921875" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-09T03:09:29.000Z" objectID="{8F196553-BA68-46B8-A5A9-2FFC33D426C2}{36}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{02B056AB-F323-07EF-1A65-E63757257CC0}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-26T06:36:21.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="17.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-09T03:09:19.000Z" objectID="{CCC527B0-CF96-0F88-31E2-BD07CD56F03F}{15}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[]]></one:T></one:OE></one:Title></one:Page> |
| 3 | style='font-weight:bold' lang=en-US>Body child &#129408; é</span><span | ||
| 4 | lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/page-lifecycle/creation/cold/read/page-001.xml+1-1| ... | @@ -1,2 +1,2 @@ | ... | @@ -1,2 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{067A6101-C793-0F15-0101-74668CB91D5E}{1}{B0}" name="New 🦋 é" dateTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:23:59.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Rust author" lastModifiedBy="Rust author" creationTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:22:39.000Z" objectID="{067A6101-C793-0F15-0101-74668CB91D5E}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[New &#129419; é]]></one:T></one:OE></one:Title></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{0F6FFE25-3632-0AD1-3AE1-41B9FD2DEB48}{1}{B0}" name="Untitled page" dateTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T06:36:22.000Z" pageLevel="1"><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings></one:Page> |
corpus/page-lifecycle/creation/cold/read/page-002.xml+3-1| ... | @@ -1,2 +1,4 @@ | ... | @@ -1,2 +1,4 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{266FCA0A-41B5-03F0-0ADE-86136233B704}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-09T04:23:58.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="17.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-09T03:09:19.000Z" objectID="{E81ABB11-7D00-0B97-2159-DD23F8403BFB}{15}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[]]></one:T></one:OE></one:Title></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{21928C81-EAD3-460F-9D55-508ABD7934A2}{1}{B0}" name="child" dateTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-26T06:36:21.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-09T03:09:29.000Z" objectID="{ABC6F9F2-08FE-42A7-B512-4FD806C2ED06}{28}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[child]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{ABC6F9F2-08FE-42A7-B512-4FD806C2ED06}{35}{B0}"><one:Position x="36.0" y="86.4000015258789" z="0"/><one:Size width="149.6102294921875" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-09T03:09:29.000Z" objectID="{ABC6F9F2-08FE-42A7-B512-4FD806C2ED06}{36}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span |
| 3 | style='font-weight:bold' lang=en-US>Body child &#129408; é</span><span | ||
| 4 | lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/page-lifecycle/creation/cold/read/page-003.xml+1-1| ... | @@ -1,2 +1,2 @@ | ... | @@ -1,2 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{3894C714-5BE5-03FC-208C-EFB4E9A37951}{1}{B0}" name="Untitled page" dateTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:23:59.000Z" pageLevel="1"><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{48DD2F58-6388-0E7D-0E26-E616BAB5B7EE}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-26T06:36:21.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="17.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-09T03:09:19.000Z" objectID="{6739EA83-CCDD-0264-254C-9EA896D3696C}{15}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[]]></one:T></one:OE></one:Title></one:Page> |
corpus/page-lifecycle/creation/cold/read/page-004.xml+1-1| ... | @@ -1,2 +1,2 @@ | ... | @@ -1,2 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{39C1FCAE-45F8-0FA4-3FC6-46AF3A75DC8A}{1}{B0}" name="Untitled page" dateTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:23:58.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Rust author" lastModifiedBy="Rust author" creationTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:22:39.000Z" objectID="{39C1FCAE-45F8-0FA4-3FC6-46AF3A75DC8A}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[]]></one:T></one:OE></one:Title></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{677392C8-6B9B-099F-32D5-118DD212AA11}{1}{B0}" name="Untitled page" dateTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T06:36:21.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Rust author" lastModifiedBy="Rust author" creationTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T06:32:12.000Z" objectID="{677392C8-6B9B-099F-32D5-118DD212AA11}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[]]></one:T></one:OE></one:Title></one:Page> |
corpus/page-lifecycle/creation/cold/read/page-005.xml+1-1| ... | @@ -1,2 +1,2 @@ | ... | @@ -1,2 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{4C0D72CA-D8C6-0D40-0E00-40C67411544F}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-09T04:23:58.000Z" pageLevel="1"><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{68D2EE6B-6A50-095F-1EBB-20E241079F8B}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-26T06:36:21.000Z" pageLevel="1"><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings></one:Page> |
corpus/page-lifecycle/creation/cold/read/page-006.xml+2-2| ... | @@ -1,4 +1,4 @@ | ... | @@ -1,4 +1,4 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{5891D2E3-C28F-4C4F-91C8-E802A2DDA3B0}{1}{B0}" name="grandchild" dateTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-09T04:23:58.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{C844F950-C3B5-4FE0-ADCD-588A8B55C0F1}{32}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[grandchild]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{C844F950-C3B5-4FE0-ADCD-588A8B55C0F1}{39}{B0}"><one:Position x="36.0" y="86.4000015258789" z="0"/><one:Size width="175.9715728759766" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{C844F950-C3B5-4FE0-ADCD-588A8B55C0F1}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{7C1EA712-6C95-4C09-A3B8-685EF953A053}{1}{B0}" name="Same title" dateTime="2026-09-09T03:09:27.000Z" lastModifiedTime="2026-09-26T06:36:21.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-09T03:09:27.000Z" lastModifiedTime="2026-09-09T03:09:27.000Z" objectID="{7E053E68-3833-469C-9187-A251D7A8BEEE}{32}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:29.000Z" objectID="{7E053E68-3833-469C-9187-A251D7A8BEEE}{39}{B0}"><one:Position x="36.0" y="86.4000015258789" z="0"/><one:Size width="158.2952728271484" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:27.000Z" lastModifiedTime="2026-09-09T03:09:29.000Z" objectID="{7E053E68-3833-469C-9187-A251D7A8BEEE}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span |
| 3 | style='font-weight:bold' lang=en-US>Body grandchild &#129408; é</span><span | 3 | style='font-weight:bold' lang=en-US>Body parent &#129408; é</span><span |
| 4 | lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> | 4 | lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/page-lifecycle/creation/cold/read/page-007.xml+2-2| ... | @@ -1,4 +1,4 @@ | ... | @@ -1,4 +1,4 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{58C13BB3-DE03-4816-B303-087ACC456B97}{1}{B0}" name="Same title" dateTime="2026-09-09T03:09:27.000Z" lastModifiedTime="2026-09-09T04:23:58.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-09T03:09:27.000Z" lastModifiedTime="2026-09-09T03:09:27.000Z" objectID="{5ADAA2C9-8AA5-4283-813C-C275E2BE752A}{32}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:29.000Z" objectID="{5ADAA2C9-8AA5-4283-813C-C275E2BE752A}{39}{B0}"><one:Position x="36.0" y="86.4000015258789" z="0"/><one:Size width="158.2952728271484" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:27.000Z" lastModifiedTime="2026-09-09T03:09:29.000Z" objectID="{5ADAA2C9-8AA5-4283-813C-C275E2BE752A}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{7C4E4E42-7019-4850-8173-882697CB6874}{1}{B0}" name="grandchild" dateTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-26T06:36:21.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{EC9B65F1-7123-4BFF-BD76-38AEBE430B35}{32}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[grandchild]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{EC9B65F1-7123-4BFF-BD76-38AEBE430B35}{39}{B0}"><one:Position x="36.0" y="86.4000015258789" z="0"/><one:Size width="175.9715728759766" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{EC9B65F1-7123-4BFF-BD76-38AEBE430B35}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span |
| 3 | style='font-weight:bold' lang=en-US>Body parent &#129408; é</span><span | 3 | style='font-weight:bold' lang=en-US>Body grandchild &#129408; é</span><span |
| 4 | lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> | 4 | lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/page-lifecycle/creation/cold/read/page-008.xml+1-1| ... | @@ -1,2 +1,2 @@ | ... | @@ -1,2 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{6C02B3F9-D11E-0A62-1E9D-86328FA37C2A}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-09T04:23:58.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="17.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-09T03:09:19.000Z" objectID="{43E67622-7E4B-067B-35F7-FE8CA3C5A2A8}{15}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[]]></one:T></one:OE></one:Title></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{82AE846C-D1FD-07B3-23E5-002BB0B8F1F6}{1}{B0}" name="New 🦋 é" dateTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T06:36:22.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Rust author" lastModifiedBy="Rust author" creationTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T06:32:12.000Z" objectID="{82AE846C-D1FD-07B3-23E5-002BB0B8F1F6}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[New &#129419; é]]></one:T></one:OE></one:Title></one:Page> |
corpus/page-lifecycle/creation/cold/read/page-009.xml+1-1| ... | @@ -1,4 +1,4 @@ | ... | @@ -1,4 +1,4 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{8B5220CE-1103-4F42-AD6F-443875AA4EFC}{1}{B0}" name="Renamed 🦋 é" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T04:23:58.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-09T03:09:34.000Z" lastModifiedTime="2026-09-09T03:09:34.000Z" objectID="{A6AAC17A-F7B8-4832-BB11-A47AA3FCCB83}{16}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Renamed &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{EDDDEAD0-9F85-44A8-BE51-3B1A009C7ABD}{39}{B0}"><one:Position x="36.0" y="86.4000015258789" z="0"/><one:Size width="170.2942504882812" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{EDDDEAD0-9F85-44A8-BE51-3B1A009C7ABD}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{AF8DBC6F-A395-4B5D-BDD4-241C40BC8538}{1}{B0}" name="Renamed 🦋 é" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-26T06:36:21.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-09T03:09:34.000Z" lastModifiedTime="2026-09-09T03:09:34.000Z" objectID="{82755DDB-452E-4C2D-ABAA-C45E96EA0047}{16}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Renamed &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{C9027671-2D13-40B7-AEEA-5B3E358AB179}{39}{B0}"><one:Position x="36.0" y="86.4000015258789" z="0"/><one:Size width="170.2942504882812" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{C9027671-2D13-40B7-AEEA-5B3E358AB179}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span |
| 3 | style='font-weight:bold' lang=en-US>Body duplicate &#129408; é</span><span | 3 | style='font-weight:bold' lang=en-US>Body duplicate &#129408; é</span><span |
| 4 | lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> | 4 | lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/page-lifecycle/creation/cold/read/page-010.xml+1-1| ... | @@ -1,4 +1,4 @@ | ... | @@ -1,4 +1,4 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{9BFEBE8D-72F6-4211-B975-2E62E65AF7D7}{1}{B0}" name="Body automatic 🦀 é preserved" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T04:23:59.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{2C80C4C5-DA5C-4DF0-BBB7-F55CC50454C4}{14}{B0}"><one:Position x="36.0" y="14.40000057220459" z="0"/><one:Size width="174.3547973632812" height="13.42771625518799"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{2C80C4C5-DA5C-4DF0-BBB7-F55CC50454C4}{15}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{BF21222C-C060-460E-A9CE-4E46D34C3C13}{1}{B0}" name="Body automatic 🦀 é preserved" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-26T06:36:21.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{085F5864-68CA-49EF-AB0C-9578F0129F00}{14}{B0}"><one:Position x="36.0" y="14.40000057220459" z="0"/><one:Size width="174.3547973632812" height="13.42771625518799"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{085F5864-68CA-49EF-AB0C-9578F0129F00}{15}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span |
| 3 | style='font-weight:bold' lang=en-US>Body automatic &#129408; é</span><span | 3 | style='font-weight:bold' lang=en-US>Body automatic &#129408; é</span><span |
| 4 | lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> | 4 | lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/page-lifecycle/creation/cold/read/page-011.xml+3-1| ... | @@ -1,2 +1,4 @@ | ... | @@ -1,2 +1,4 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{C6BB9F1B-5105-064D-086F-0B103A30C43B}{1}{B0}" name="New 🦋 é" dateTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:23:59.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Rust author" lastModifiedBy="Rust author" creationTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:22:39.000Z" objectID="{C6BB9F1B-5105-064D-086F-0B103A30C43B}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[New &#129419; é]]></one:T></one:OE></one:Title></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{C46846CF-9569-416C-8274-2647D7851122}{1}{B0}" name="Body trailing 🦀 é preserved" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-26T06:36:21.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-09T03:09:34.000Z" lastModifiedTime="2026-09-09T03:09:34.000Z" objectID="{81E65742-9824-44DE-97A5-9A479B990192}{16}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{555966B2-BAF9-4B5C-B021-44A3F102FB6E}{39}{B0}"><one:Position x="36.0" y="86.4000015258789" z="0"/><one:Size width="160.1752014160156" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{555966B2-BAF9-4B5C-B021-44A3F102FB6E}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span |
| 3 | style='font-weight:bold' lang=en-US>Body trailing &#129408; é</span><span | ||
| 4 | lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/page-lifecycle/creation/cold/read/page-012.xml+1-3| ... | @@ -1,4 +1,2 @@ | ... | @@ -1,4 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{E0B7DA6E-27FF-4573-92CF-4663E293DAE6}{1}{B0}" name="Body trailing 🦀 é preserved" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T04:23:59.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-09T03:09:34.000Z" lastModifiedTime="2026-09-09T03:09:34.000Z" objectID="{A539CBE3-2AB2-40C1-871E-FA63AE8FCA56}{16}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{7186FA13-086F-4F43-A09A-2487C41430AA}{39}{B0}"><one:Position x="36.0" y="86.4000015258789" z="0"/><one:Size width="160.1752014160156" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{7186FA13-086F-4F43-A09A-2487C41430AA}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{CCABB477-9855-0E14-1DCB-06D00F5CFE0E}{1}{B0}" name="New 🦋 é" dateTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T06:36:22.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Rust author" lastModifiedBy="Rust author" creationTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T06:32:12.000Z" objectID="{CCABB477-9855-0E14-1DCB-06D00F5CFE0E}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[New &#129419; é]]></one:T></one:OE></one:Title></one:Page> |
| 3 | style='font-weight:bold' lang=en-US>Body trailing &#129408; é</span><span | ||
| 4 | lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/page-lifecycle/creation/followup/Lifecycle.one| Binary files a/corpus/page-lifecycle/creation/followup/Lifecycle.one and b/corpus/page-lifecycle/creation/followup/Lifecycle.one differ | |||
corpus/page-lifecycle/creation/followup/cold/notebook/Lifecycle.one| Binary files a/corpus/page-lifecycle/creation/followup/cold/notebook/Lifecycle.one and b/corpus/page-lifecycle/creation/followup/cold/notebook/Lifecycle.one differ | |||
corpus/page-lifecycle/creation/followup/cold/read/hierarchy.xml+1-1| ... | @@ -1,2 +1,2 @@ | ... | @@ -1,2 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Notebook xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" name="notebook" nickname="notebook" ID="{6DFB689D-2E8E-4686-9997-32046753F9FD}{1}{B0}" path="C:\one-tests\runs\capture\notebook" lastModifiedTime="2026-09-09T04:44:45.000Z" color="#B49EDE"><one:Section name="Lifecycle" ID="{C3953BFE-3B75-0740-087B-6C4AD8A1EE03}{1}{B0}" path="C:\one-tests\runs\capture\notebook\Lifecycle.one" lastModifiedTime="2026-09-09T04:44:45.000Z" color="#91BAAE"><one:Page ID="{3C0154AF-70B8-0313-287E-3C2E59ADA71D}{1}{B0}" name="Untitled page" dateTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:44:45.000Z" pageLevel="1"/><one:Page ID="{23AF620B-74F5-0F47-1D66-FC9201EBCC93}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-09T04:44:45.000Z" pageLevel="1"/><one:Page ID="{69C21BF8-E45E-06D5-0925-FCB3EC7B07BD}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-09T04:44:45.000Z" pageLevel="1"/><one:Page ID="{49CDDACB-ED86-01F7-19B8-3A4717C92FD8}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-09T04:44:45.000Z" pageLevel="1"/><one:Page ID="{5D0193B2-EB43-44A1-A4BB-72FBAF9D1000}{1}{B0}" name="Same title" dateTime="2026-09-09T03:09:27.000Z" lastModifiedTime="2026-09-09T04:44:45.000Z" pageLevel="1"/><one:Page ID="{008DB821-6D05-4EA7-9A56-4A2FEBB784F1}{1}{B0}" name="child" dateTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-09T04:44:45.000Z" pageLevel="1"/><one:Page ID="{5D517AE2-F7CF-40F8-8670-9283C105D827}{1}{B0}" name="grandchild" dateTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-09T04:44:45.000Z" pageLevel="1"/><one:Page ID="{8E9288CF-2443-43F5-BAD7-3EB91672356B}{1}{B0}" name="Renamed 🦋 é" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T04:44:45.000Z" pageLevel="1"/><one:Page ID="{E577726F-12BF-49C4-8577-3CE2814BA171}{1}{B0}" name="Body trailing 🦀 é preserved" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T04:44:45.000Z" pageLevel="1"/><one:Page ID="{9E3E168C-47B6-4EA6-AECD-54E385828C40}{1}{B0}" name="Body automatic 🦀 é preserved" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T04:44:45.000Z" pageLevel="1"/><one:Page ID="{3D546F15-6EA5-0F4B-3734-95358A7B02C6}{1}{B0}" name="Untitled page" dateTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:44:45.000Z" pageLevel="1"/><one:Page ID="{03BAC900-F2D3-03A2-16B9-0EE7EF6166C9}{1}{B0}" name="Reviewed New 🦋 é" dateTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:44:45.000Z" pageLevel="1"/><one:Page ID="{C37B371A-6445-0AFA-1FD7-719159E8BFAC}{1}{B0}" name="Reviewed New 🦋 é" dateTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:44:45.000Z" pageLevel="1"/></one:Section></one:Notebook> | 2 | <one:Notebook xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" name="notebook" nickname="notebook" ID="{9911D09B-348F-485C-A103-C4023440DE63}{1}{B0}" path="C:\one-tests\runs\capture\notebook" lastModifiedTime="2026-09-26T07:00:22.000Z" color="#B49EDE"><one:Section name="Lifecycle" ID="{4BBA0B35-96CC-0ED2-1A80-B96B6AA11DAF}{1}{B0}" path="C:\one-tests\runs\capture\notebook\Lifecycle.one" lastModifiedTime="2026-09-26T07:00:22.000Z" color="#91BAAE"><one:Page ID="{CE4396A3-41F4-08A5-272D-DE0936DCE9EE}{1}{B0}" name="Untitled page" dateTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T07:00:13.000Z" pageLevel="1"/><one:Page ID="{AB8052C0-D94C-06D5-0F9D-29B3B3EB3F3F}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-26T07:00:13.000Z" pageLevel="1"/><one:Page ID="{E1ED2B33-49E7-0F47-1BDE-29925E7BF411}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-26T07:00:13.000Z" pageLevel="1"/><one:Page ID="{C1E2EA00-403F-0865-0B43-EF66A5C9DC74}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-26T07:00:13.000Z" pageLevel="1"/><one:Page ID="{D52EA379-46FA-4D33-B640-A7DA1D9DE3AC}{1}{B0}" name="Same title" dateTime="2026-09-09T03:09:27.000Z" lastModifiedTime="2026-09-26T07:00:13.000Z" pageLevel="1"/><one:Page ID="{88A288EA-C0BC-4735-88AD-9F0E59B7775D}{1}{B0}" name="child" dateTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-26T07:00:13.000Z" pageLevel="1"/><one:Page ID="{D57E4A29-5A76-496A-948B-47A273052B8B}{1}{B0}" name="grandchild" dateTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-26T07:00:13.000Z" pageLevel="1"/><one:Page ID="{06BDB804-89FA-4A67-A82C-EB98A472C6C7}{1}{B0}" name="Renamed 🦋 é" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-26T07:00:13.000Z" pageLevel="1"/><one:Page ID="{6D5842A4-BF06-4056-978C-E9C3334B52DD}{1}{B0}" name="Body trailing 🦀 é preserved" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-26T07:00:13.000Z" pageLevel="1"/><one:Page ID="{16112647-EA0F-4734-BC36-81C237827FEC}{1}{B0}" name="Body automatic 🦀 é preserved" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-26T07:00:13.000Z" pageLevel="1"/><one:Page ID="{A65FFA4E-1C5D-0BEB-2F19-8E3D19E3A8B7}{1}{B0}" name="Untitled page" dateTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T07:00:13.000Z" pageLevel="1"/><one:Page ID="{659BB01C-B23A-0F2E-0833-C954EB92BDF1}{1}{B0}" name="Reviewed New 🦋 é" dateTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T07:00:13.000Z" pageLevel="1"/><one:Page ID="{2B9E8007-FB92-0689-361D-CFAF5476B209}{1}{B0}" name="Reviewed New 🦋 é" dateTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T07:00:13.000Z" pageLevel="1"/></one:Section></one:Notebook> |
corpus/page-lifecycle/creation/followup/cold/read/page-000.xml+2-2| ... | @@ -1,4 +1,4 @@ | ... | @@ -1,4 +1,4 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{008DB821-6D05-4EA7-9A56-4A2FEBB784F1}{1}{B0}" name="child" dateTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-09T04:44:45.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-09T03:09:29.000Z" objectID="{8AD9CD52-8F28-4A0F-B211-557D500C5D55}{28}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[child]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{8AD9CD52-8F28-4A0F-B211-557D500C5D55}{35}{B0}"><one:Position x="36.0" y="86.4000015258789" z="0"/><one:Size width="149.6102294921875" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-09T03:09:29.000Z" objectID="{8AD9CD52-8F28-4A0F-B211-557D500C5D55}{36}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{06BDB804-89FA-4A67-A82C-EB98A472C6C7}{1}{B0}" name="Renamed 🦋 é" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-26T07:00:13.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-09T03:09:34.000Z" lastModifiedTime="2026-09-09T03:09:34.000Z" objectID="{2B4559B0-6F41-4D17-BE52-0BDA722443B8}{16}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Renamed &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{6032721A-077C-418D-BB12-94BAD144F286}{39}{B0}"><one:Position x="36.0" y="86.4000015258789" z="0"/><one:Size width="170.2942504882812" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{6032721A-077C-418D-BB12-94BAD144F286}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span |
| 3 | style='font-weight:bold' lang=en-US>Body child &#129408; é</span><span | 3 | style='font-weight:bold' lang=en-US>Body duplicate &#129408; é</span><span |
| 4 | lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> | 4 | lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/page-lifecycle/creation/followup/cold/read/page-001.xml+3-1| ... | @@ -1,2 +1,4 @@ | ... | @@ -1,2 +1,4 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{03BAC900-F2D3-03A2-16B9-0EE7EF6166C9}{1}{B0}" name="Reviewed New 🦋 é" dateTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:44:45.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:QuickStyleDef index="1" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Rust author" lastModifiedBy="Rust author" creationTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:42:47.000Z" objectID="{03BAC900-F2D3-03A2-16B9-0EE7EF6166C9}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Reviewed New &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T04:42:47.000Z" objectID="{80CDD07A-F21F-468D-A2FA-A92C4EC6F53F}{10}{B0}"><one:Position x="72.0" y="144.0" z="0"/><one:Size width="244.6467742919922" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T04:33:45.000Z" lastModifiedTime="2026-09-09T04:42:47.000Z" objectID="{80CDD07A-F21F-468D-A2FA-A92C4EC6F53F}{11}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Rust + Native body 0 after Rust page creation.]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{16112647-EA0F-4734-BC36-81C237827FEC}{1}{B0}" name="Body automatic 🦀 é preserved" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-26T07:00:13.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{A16F5C0F-42A5-48D5-BEF4-5AFC14DCDCFF}{14}{B0}"><one:Position x="36.0" y="14.40000057220459" z="0"/><one:Size width="174.3547973632812" height="13.42771625518799"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{A16F5C0F-42A5-48D5-BEF4-5AFC14DCDCFF}{15}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span |
| 3 | style='font-weight:bold' lang=en-US>Body automatic &#129408; é</span><span | ||
| 4 | lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/page-lifecycle/creation/followup/cold/read/page-002.xml+1-1| ... | @@ -1,2 +1,2 @@ | ... | @@ -1,2 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{23AF620B-74F5-0F47-1D66-FC9201EBCC93}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-09T04:44:45.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="17.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-09T03:09:19.000Z" objectID="{EDDA1310-4840-0720-36E1-A7A29B98406C}{15}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[]]></one:T></one:OE></one:Title></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{2B9E8007-FB92-0689-361D-CFAF5476B209}{1}{B0}" name="Reviewed New 🦋 é" dateTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T07:00:13.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:QuickStyleDef index="1" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Rust author" lastModifiedBy="Rust author" creationTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T06:58:25.000Z" objectID="{2B9E8007-FB92-0689-361D-CFAF5476B209}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Reviewed New &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-26T06:58:24.000Z" objectID="{7EFD74E4-6962-403F-8EFD-EE1FA6BD77EC}{10}{B0}"><one:Position x="72.0" y="144.0" z="0"/><one:Size width="244.6467742919922" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-26T06:51:05.000Z" lastModifiedTime="2026-09-26T06:58:24.000Z" objectID="{7EFD74E4-6962-403F-8EFD-EE1FA6BD77EC}{11}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Rust + Native body 1 after Rust page creation.]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/page-lifecycle/creation/followup/cold/read/page-003.xml+1-1| ... | @@ -1,2 +1,2 @@ | ... | @@ -1,2 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{3C0154AF-70B8-0313-287E-3C2E59ADA71D}{1}{B0}" name="Untitled page" dateTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:44:45.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Rust author" lastModifiedBy="Rust author" creationTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:22:39.000Z" objectID="{3C0154AF-70B8-0313-287E-3C2E59ADA71D}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[]]></one:T></one:OE></one:Title></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{659BB01C-B23A-0F2E-0833-C954EB92BDF1}{1}{B0}" name="Reviewed New 🦋 é" dateTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T07:00:13.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:QuickStyleDef index="1" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Rust author" lastModifiedBy="Rust author" creationTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T06:58:24.000Z" objectID="{659BB01C-B23A-0F2E-0833-C954EB92BDF1}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Reviewed New &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-26T06:58:24.000Z" objectID="{10234C69-755D-4058-B87F-1D7258B660AD}{10}{B0}"><one:Position x="72.0" y="144.0" z="0"/><one:Size width="244.6467742919922" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-26T06:51:05.000Z" lastModifiedTime="2026-09-26T06:58:24.000Z" objectID="{10234C69-755D-4058-B87F-1D7258B660AD}{11}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Rust + Native body 0 after Rust page creation.]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/page-lifecycle/creation/followup/cold/read/page-004.xml+3-1| ... | @@ -1,2 +1,4 @@ | ... | @@ -1,2 +1,4 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{3D546F15-6EA5-0F4B-3734-95358A7B02C6}{1}{B0}" name="Untitled page" dateTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:44:45.000Z" pageLevel="1"><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{6D5842A4-BF06-4056-978C-E9C3334B52DD}{1}{B0}" name="Body trailing 🦀 é preserved" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-26T07:00:13.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-09T03:09:34.000Z" lastModifiedTime="2026-09-09T03:09:34.000Z" objectID="{28D65329-B24B-45E4-825D-55C37F57426D}{16}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{FC6962D9-9096-4A66-A5D9-8B2715CCB891}{39}{B0}"><one:Position x="36.0" y="86.4000015258789" z="0"/><one:Size width="160.1752014160156" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{FC6962D9-9096-4A66-A5D9-8B2715CCB891}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span |
| 3 | style='font-weight:bold' lang=en-US>Body trailing &#129408; é</span><span | ||
| 4 | lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/page-lifecycle/creation/followup/cold/read/page-005.xml+3-1| ... | @@ -1,2 +1,4 @@ | ... | @@ -1,2 +1,4 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{49CDDACB-ED86-01F7-19B8-3A4717C92FD8}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-09T04:44:45.000Z" pageLevel="1"><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{88A288EA-C0BC-4735-88AD-9F0E59B7775D}{1}{B0}" name="child" dateTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-26T07:00:13.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-09T03:09:29.000Z" objectID="{02F6FD99-2291-439D-A0EA-805CE20CAEF9}{28}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[child]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{02F6FD99-2291-439D-A0EA-805CE20CAEF9}{35}{B0}"><one:Position x="36.0" y="86.4000015258789" z="0"/><one:Size width="149.6102294921875" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-09T03:09:29.000Z" objectID="{02F6FD99-2291-439D-A0EA-805CE20CAEF9}{36}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span |
| 3 | style='font-weight:bold' lang=en-US>Body child &#129408; é</span><span | ||
| 4 | lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/page-lifecycle/creation/followup/cold/read/page-006.xml+1-3| ... | @@ -1,4 +1,2 @@ | ... | @@ -1,4 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{5D0193B2-EB43-44A1-A4BB-72FBAF9D1000}{1}{B0}" name="Same title" dateTime="2026-09-09T03:09:27.000Z" lastModifiedTime="2026-09-09T04:44:45.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-09T03:09:27.000Z" lastModifiedTime="2026-09-09T03:09:27.000Z" objectID="{5F1A0AC8-BFE5-4E34-9684-B8F481660EBD}{32}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:29.000Z" objectID="{5F1A0AC8-BFE5-4E34-9684-B8F481660EBD}{39}{B0}"><one:Position x="36.0" y="86.4000015258789" z="0"/><one:Size width="158.2952728271484" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:27.000Z" lastModifiedTime="2026-09-09T03:09:29.000Z" objectID="{5F1A0AC8-BFE5-4E34-9684-B8F481660EBD}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{A65FFA4E-1C5D-0BEB-2F19-8E3D19E3A8B7}{1}{B0}" name="Untitled page" dateTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T07:00:13.000Z" pageLevel="1"><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings></one:Page> |
| 3 | style='font-weight:bold' lang=en-US>Body parent &#129408; é</span><span | ||
| 4 | lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/page-lifecycle/creation/followup/cold/read/page-007.xml+1-3| ... | @@ -1,4 +1,2 @@ | ... | @@ -1,4 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{5D517AE2-F7CF-40F8-8670-9283C105D827}{1}{B0}" name="grandchild" dateTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-09T04:44:45.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{CD845151-F6F5-4357-BA75-220BE88DBB66}{32}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[grandchild]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{CD845151-F6F5-4357-BA75-220BE88DBB66}{39}{B0}"><one:Position x="36.0" y="86.4000015258789" z="0"/><one:Size width="175.9715728759766" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{CD845151-F6F5-4357-BA75-220BE88DBB66}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{AB8052C0-D94C-06D5-0F9D-29B3B3EB3F3F}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-26T07:00:13.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="17.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-09T03:09:19.000Z" objectID="{65F523DB-E5F9-0EB2-241A-72832998B3C0}{15}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[]]></one:T></one:OE></one:Title></one:Page> |
| 3 | style='font-weight:bold' lang=en-US>Body grandchild &#129408; é</span><span | ||
| 4 | lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/page-lifecycle/creation/followup/cold/read/page-008.xml+1-1| ... | @@ -1,2 +1,2 @@ | ... | @@ -1,2 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{69C21BF8-E45E-06D5-0925-FCB3EC7B07BD}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-09T04:44:45.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="17.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-09T03:09:19.000Z" objectID="{4626DE23-4B0B-0ACC-224F-840DC01DD93F}{15}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[]]></one:T></one:OE></one:Title></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{C1E2EA00-403F-0865-0B43-EF66A5C9DC74}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-26T07:00:13.000Z" pageLevel="1"><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings></one:Page> |
corpus/page-lifecycle/creation/followup/cold/read/page-009.xml+1-3| ... | @@ -1,4 +1,2 @@ | ... | @@ -1,4 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{8E9288CF-2443-43F5-BAD7-3EB91672356B}{1}{B0}" name="Renamed 🦋 é" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T04:44:45.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-09T03:09:34.000Z" lastModifiedTime="2026-09-09T03:09:34.000Z" objectID="{A36A697B-C2F8-4485-ACA9-DEFBC024B014}{16}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Renamed &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{E81D42D1-AAC5-481F-A9E9-419B6344012A}{39}{B0}"><one:Position x="36.0" y="86.4000015258789" z="0"/><one:Size width="170.2942504882812" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{E81D42D1-AAC5-481F-A9E9-419B6344012A}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{CE4396A3-41F4-08A5-272D-DE0936DCE9EE}{1}{B0}" name="Untitled page" dateTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T07:00:13.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Rust author" lastModifiedBy="Rust author" creationTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T06:32:12.000Z" objectID="{CE4396A3-41F4-08A5-272D-DE0936DCE9EE}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[]]></one:T></one:OE></one:Title></one:Page> |
| 3 | style='font-weight:bold' lang=en-US>Body duplicate &#129408; é</span><span | ||
| 4 | lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/page-lifecycle/creation/followup/cold/read/page-010.xml+2-2| ... | @@ -1,4 +1,4 @@ | ... | @@ -1,4 +1,4 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{9E3E168C-47B6-4EA6-AECD-54E385828C40}{1}{B0}" name="Body automatic 🦀 é preserved" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T04:44:45.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{29406CC4-EF1C-4147-AC0F-8FDDA6DC2F53}{14}{B0}"><one:Position x="36.0" y="14.40000057220459" z="0"/><one:Size width="174.3547973632812" height="13.42771625518799"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{29406CC4-EF1C-4147-AC0F-8FDDA6DC2F53}{15}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{D52EA379-46FA-4D33-B640-A7DA1D9DE3AC}{1}{B0}" name="Same title" dateTime="2026-09-09T03:09:27.000Z" lastModifiedTime="2026-09-26T07:00:13.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-09T03:09:27.000Z" lastModifiedTime="2026-09-09T03:09:27.000Z" objectID="{D7353A03-125C-47A6-847F-6DD53366FD11}{32}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:29.000Z" objectID="{D7353A03-125C-47A6-847F-6DD53366FD11}{39}{B0}"><one:Position x="36.0" y="86.4000015258789" z="0"/><one:Size width="158.2952728271484" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:27.000Z" lastModifiedTime="2026-09-09T03:09:29.000Z" objectID="{D7353A03-125C-47A6-847F-6DD53366FD11}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span |
| 3 | style='font-weight:bold' lang=en-US>Body automatic &#129408; é</span><span | 3 | style='font-weight:bold' lang=en-US>Body parent &#129408; é</span><span |
| 4 | lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> | 4 | lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/page-lifecycle/creation/followup/cold/read/page-011.xml+3-1| ... | @@ -1,2 +1,4 @@ | ... | @@ -1,2 +1,4 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{C37B371A-6445-0AFA-1FD7-719159E8BFAC}{1}{B0}" name="Reviewed New 🦋 é" dateTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:44:45.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:QuickStyleDef index="1" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Rust author" lastModifiedBy="Rust author" creationTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:42:47.000Z" objectID="{C37B371A-6445-0AFA-1FD7-719159E8BFAC}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Reviewed New &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T04:42:47.000Z" objectID="{DA84E582-1E18-4AD2-984E-701D70B4CB24}{10}{B0}"><one:Position x="72.0" y="144.0" z="0"/><one:Size width="244.6467742919922" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T04:33:45.000Z" lastModifiedTime="2026-09-09T04:42:47.000Z" objectID="{DA84E582-1E18-4AD2-984E-701D70B4CB24}{11}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Rust + Native body 1 after Rust page creation.]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{D57E4A29-5A76-496A-948B-47A273052B8B}{1}{B0}" name="grandchild" dateTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-26T07:00:13.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{45AB619A-5B4C-4AC5-A88E-F72A5A8D48CA}{32}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[grandchild]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{45AB619A-5B4C-4AC5-A88E-F72A5A8D48CA}{39}{B0}"><one:Position x="36.0" y="86.4000015258789" z="0"/><one:Size width="175.9715728759766" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{45AB619A-5B4C-4AC5-A88E-F72A5A8D48CA}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span |
| 3 | style='font-weight:bold' lang=en-US>Body grandchild &#129408; é</span><span | ||
| 4 | lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/page-lifecycle/creation/followup/cold/read/page-012.xml+1-3| ... | @@ -1,4 +1,2 @@ | ... | @@ -1,4 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{E577726F-12BF-49C4-8577-3CE2814BA171}{1}{B0}" name="Body trailing 🦀 é preserved" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T04:44:45.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-09T03:09:34.000Z" lastModifiedTime="2026-09-09T03:09:34.000Z" objectID="{A0F963E2-1FF2-4C76-90A6-80E2CD57B1C1}{16}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{74465212-3D2F-43F4-B722-5E06A7CC4B3D}{39}{B0}"><one:Position x="36.0" y="86.4000015258789" z="0"/><one:Size width="160.1752014160156" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{74465212-3D2F-43F4-B722-5E06A7CC4B3D}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{E1ED2B33-49E7-0F47-1BDE-29925E7BF411}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-26T07:00:13.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="17.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-09T03:09:19.000Z" objectID="{CE09EEE8-E6B2-035E-30B4-512C721D2A93}{15}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[]]></one:T></one:OE></one:Title></one:Page> |
| 3 | style='font-weight:bold' lang=en-US>Body trailing &#129408; é</span><span | ||
| 4 | lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/page-lifecycle/creation/native/cold/read/hierarchy.xml+1-1| ... | @@ -1,2 +1,2 @@ | ... | @@ -1,2 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Notebook xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" name="notebook" nickname="notebook" ID="{89446429-1F4A-475B-A18F-A3C179D6E0AB}{1}{B0}" path="C:\one-tests\runs\capture\notebook" lastModifiedTime="2026-09-09T04:37:57.000Z" color="#B49EDE"><one:Section name="Lifecycle" ID="{82D2B6AE-B1AA-0F47-17A3-639D555107BE}{1}{B0}" path="C:\one-tests\runs\capture\notebook\Lifecycle.one" lastModifiedTime="2026-09-09T04:37:57.000Z" color="#91BAAE"><one:Page ID="{7D46D9FF-FA67-0B14-37A6-33F9D45D4EA0}{1}{B0}" name="Untitled page" dateTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:37:57.000Z" pageLevel="1"/><one:Page ID="{62E8EF5B-FE2A-0740-02BE-F3458C1B252E}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-09T04:37:57.000Z" pageLevel="1"/><one:Page ID="{288596A8-6E81-0ED2-16FD-F364618BEE00}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-09T04:37:57.000Z" pageLevel="1"/><one:Page ID="{088A579B-6759-09F0-0660-35909A39C665}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-09T04:37:57.000Z" pageLevel="1"/><one:Page ID="{1C461EE2-619C-4CA6-BB63-7D2C226DF9BD}{1}{B0}" name="Same title" dateTime="2026-09-09T03:09:27.000Z" lastModifiedTime="2026-09-09T04:37:57.000Z" pageLevel="1"/><one:Page ID="{41CA3571-E7DA-46A0-858E-45F866476D4C}{1}{B0}" name="child" dateTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-09T04:37:57.000Z" pageLevel="1"/><one:Page ID="{1C16F7B2-7D10-48FF-99A8-9D544CF5319A}{1}{B0}" name="grandchild" dateTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-09T04:37:57.000Z" pageLevel="1"/><one:Page ID="{CFD5059F-AE9C-4BF2-A50F-316E9B82DCD6}{1}{B0}" name="Renamed 🦋 é" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T04:37:57.000Z" pageLevel="1"/><one:Page ID="{A430FF3F-9860-41C3-9AAF-33350CBB48CC}{1}{B0}" name="Body trailing 🦀 é preserved" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T04:37:57.000Z" pageLevel="1"/><one:Page ID="{DF799BDC-CD69-46A1-B115-5B34087265FD}{1}{B0}" name="Body automatic 🦀 é preserved" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T04:37:57.000Z" pageLevel="1"/><one:Page ID="{7C13E245-E47A-074C-28EC-9AE2078BEB7B}{1}{B0}" name="Untitled page" dateTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:37:57.000Z" pageLevel="1"/><one:Page ID="{42FD4450-780C-0BA5-0961-013062918F74}{1}{B0}" name="New 🦋 é" dateTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:37:57.000Z" pageLevel="1"/><one:Page ID="{823CBA4A-EE9A-02FD-000F-7E46D4185611}{1}{B0}" name="New 🦋 é" dateTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:37:57.000Z" pageLevel="1"/></one:Section></one:Notebook> | 2 | <one:Notebook xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" name="notebook" nickname="notebook" ID="{D359911F-7062-45AF-8CB2-62243A804169}{1}{B0}" path="C:\one-tests\runs\capture\notebook" lastModifiedTime="2026-09-26T06:56:14.000Z" color="#B49EDE"><one:Section name="Lifecycle" ID="{78DF607A-1198-074B-3AAD-512A7379337A}{1}{B0}" path="C:\one-tests\runs\capture\notebook\Lifecycle.one" lastModifiedTime="2026-09-26T06:56:14.000Z" color="#91BAAE"><one:Page ID="{FD26FDEC-C6A0-013C-0700-36482F04C73B}{1}{B0}" name="Untitled page" dateTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T06:56:13.000Z" pageLevel="1"/><one:Page ID="{98E5398F-5E18-0F4C-2FB0-C1F2AA3311EA}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-26T06:56:13.000Z" pageLevel="1"/><one:Page ID="{D288407C-CEB3-06DE-3BF3-C1D347A3DAC4}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-26T06:56:13.000Z" pageLevel="1"/><one:Page ID="{F287814F-C76B-01FC-2B6E-0727BC11F2A1}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-26T06:56:13.000Z" pageLevel="1"/><one:Page ID="{E64BC836-C1AE-44AA-966D-4F9B0445CD79}{1}{B0}" name="Same title" dateTime="2026-09-09T03:09:27.000Z" lastModifiedTime="2026-09-26T06:56:13.000Z" pageLevel="1"/><one:Page ID="{BBC7E3A5-47E8-4EAC-A880-774F406F5988}{1}{B0}" name="child" dateTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-26T06:56:13.000Z" pageLevel="1"/><one:Page ID="{E61B2166-DD22-40F3-B4A6-AFE36ADD055E}{1}{B0}" name="grandchild" dateTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-26T06:56:13.000Z" pageLevel="1"/><one:Page ID="{35D8D34B-0EAE-43FE-8801-03D9BDAAE812}{1}{B0}" name="Renamed 🦋 é" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-26T06:56:14.000Z" pageLevel="1"/><one:Page ID="{5E3D29EB-3852-49CF-B7A1-01822A937C08}{1}{B0}" name="Body trailing 🦀 é preserved" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-26T06:56:14.000Z" pageLevel="1"/><one:Page ID="{25744D08-6D5B-4EAD-9C1B-69832E5A5139}{1}{B0}" name="Body automatic 🦀 é preserved" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-26T06:56:14.000Z" pageLevel="1"/><one:Page ID="{953A9101-9B09-0272-0F34-667C003B8662}{1}{B0}" name="Untitled page" dateTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T06:56:14.000Z" pageLevel="1"/><one:Page ID="{56FEDB53-356E-06B7-281E-2115F24A9324}{1}{B0}" name="New 🦋 é" dateTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T06:56:14.000Z" pageLevel="1"/><one:Page ID="{18FBEB48-7CC6-0F10-1630-27EE4DAE9CDC}{1}{B0}" name="New 🦋 é" dateTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T06:56:14.000Z" pageLevel="1"/></one:Section></one:Notebook> |
corpus/page-lifecycle/creation/native/cold/read/page-000.xml+1-1| ... | @@ -1,2 +1,2 @@ | ... | @@ -1,2 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{088A579B-6759-09F0-0660-35909A39C665}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-09T04:37:57.000Z" pageLevel="1"><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{18FBEB48-7CC6-0F10-1630-27EE4DAE9CDC}{1}{B0}" name="New 🦋 é" dateTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T06:56:14.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:QuickStyleDef index="1" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Rust author" lastModifiedBy="Rust author" creationTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T06:32:12.000Z" objectID="{18FBEB48-7CC6-0F10-1630-27EE4DAE9CDC}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[New &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-26T06:51:06.000Z" objectID="{4D981FAB-EE36-49A6-AED0-065EBF655939}{10}{B0}"><one:Position x="72.0" y="144.0" z="0"/><one:Size width="214.4557342529297" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-26T06:51:05.000Z" lastModifiedTime="2026-09-26T06:51:05.000Z" objectID="{4D981FAB-EE36-49A6-AED0-065EBF655939}{11}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Native body 1 after Rust page creation.]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/page-lifecycle/creation/native/cold/read/page-001.xml+2-2| ... | @@ -1,4 +1,4 @@ | ... | @@ -1,4 +1,4 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{1C16F7B2-7D10-48FF-99A8-9D544CF5319A}{1}{B0}" name="grandchild" dateTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-09T04:37:57.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{8CC3DC01-7C2A-4B50-A5AD-2DDC657D52DB}{32}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[grandchild]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{8CC3DC01-7C2A-4B50-A5AD-2DDC657D52DB}{39}{B0}"><one:Position x="36.0" y="86.4000015258789" z="0"/><one:Size width="175.9715728759766" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{8CC3DC01-7C2A-4B50-A5AD-2DDC657D52DB}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{25744D08-6D5B-4EAD-9C1B-69832E5A5139}{1}{B0}" name="Body automatic 🦀 é preserved" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-26T06:56:14.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{920A3740-C5F1-414C-9ED9-B2BD0D04F22A}{14}{B0}"><one:Position x="36.0" y="14.40000057220459" z="0"/><one:Size width="174.3547973632812" height="13.42771625518799"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{920A3740-C5F1-414C-9ED9-B2BD0D04F22A}{15}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span |
| 3 | style='font-weight:bold' lang=en-US>Body grandchild &#129408; é</span><span | 3 | style='font-weight:bold' lang=en-US>Body automatic &#129408; é</span><span |
| 4 | lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> | 4 | lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/page-lifecycle/creation/native/cold/read/page-002.xml+2-2| ... | @@ -1,4 +1,4 @@ | ... | @@ -1,4 +1,4 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{1C461EE2-619C-4CA6-BB63-7D2C226DF9BD}{1}{B0}" name="Same title" dateTime="2026-09-09T03:09:27.000Z" lastModifiedTime="2026-09-09T04:37:57.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-09T03:09:27.000Z" lastModifiedTime="2026-09-09T03:09:27.000Z" objectID="{1E5D8798-353A-4633-895C-B7230C96E700}{32}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:29.000Z" objectID="{1E5D8798-353A-4633-895C-B7230C96E700}{39}{B0}"><one:Position x="36.0" y="86.4000015258789" z="0"/><one:Size width="158.2952728271484" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:27.000Z" lastModifiedTime="2026-09-09T03:09:29.000Z" objectID="{1E5D8798-353A-4633-895C-B7230C96E700}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{35D8D34B-0EAE-43FE-8801-03D9BDAAE812}{1}{B0}" name="Renamed 🦋 é" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-26T06:56:14.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-09T03:09:34.000Z" lastModifiedTime="2026-09-09T03:09:34.000Z" objectID="{182032FF-E815-448E-9E7F-E39B6BFC6D6D}{16}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Renamed &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{53571955-8028-4814-9B3F-7CFBC89CDC53}{39}{B0}"><one:Position x="36.0" y="86.4000015258789" z="0"/><one:Size width="170.2942504882812" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{53571955-8028-4814-9B3F-7CFBC89CDC53}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span |
| 3 | style='font-weight:bold' lang=en-US>Body parent &#129408; é</span><span | 3 | style='font-weight:bold' lang=en-US>Body duplicate &#129408; é</span><span |
| 4 | lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> | 4 | lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/page-lifecycle/creation/native/cold/read/page-003.xml+1-1| ... | @@ -1,2 +1,2 @@ | ... | @@ -1,2 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{288596A8-6E81-0ED2-16FD-F364618BEE00}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-09T04:37:57.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="17.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-09T03:09:19.000Z" objectID="{07615373-C1D4-02CB-3D97-8BDA4DED3082}{15}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[]]></one:T></one:OE></one:Title></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{56FEDB53-356E-06B7-281E-2115F24A9324}{1}{B0}" name="New 🦋 é" dateTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T06:56:14.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:QuickStyleDef index="1" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Rust author" lastModifiedBy="Rust author" creationTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T06:32:12.000Z" objectID="{56FEDB53-356E-06B7-281E-2115F24A9324}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[New &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-26T06:51:05.000Z" objectID="{23462726-F209-49C1-9852-F533416E4E78}{10}{B0}"><one:Position x="72.0" y="144.0" z="0"/><one:Size width="214.4557342529297" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-26T06:51:05.000Z" lastModifiedTime="2026-09-26T06:51:05.000Z" objectID="{23462726-F209-49C1-9852-F533416E4E78}{11}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Native body 0 after Rust page creation.]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/page-lifecycle/creation/native/cold/read/page-004.xml+2-2| ... | @@ -1,4 +1,4 @@ | ... | @@ -1,4 +1,4 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{41CA3571-E7DA-46A0-858E-45F866476D4C}{1}{B0}" name="child" dateTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-09T04:37:57.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-09T03:09:29.000Z" objectID="{CB9E4002-05F7-4208-ADC9-5AAADDFCB4E8}{28}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[child]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{CB9E4002-05F7-4208-ADC9-5AAADDFCB4E8}{35}{B0}"><one:Position x="36.0" y="86.4000015258789" z="0"/><one:Size width="149.6102294921875" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-09T03:09:29.000Z" objectID="{CB9E4002-05F7-4208-ADC9-5AAADDFCB4E8}{36}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{5E3D29EB-3852-49CF-B7A1-01822A937C08}{1}{B0}" name="Body trailing 🦀 é preserved" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-26T06:56:14.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-09T03:09:34.000Z" lastModifiedTime="2026-09-09T03:09:34.000Z" objectID="{1BB33866-351F-4C7D-A270-BD82668F6CB8}{16}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{CF0C0996-17C2-43FF-85F4-63660C149644}{39}{B0}"><one:Position x="36.0" y="86.4000015258789" z="0"/><one:Size width="160.1752014160156" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{CF0C0996-17C2-43FF-85F4-63660C149644}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span |
| 3 | style='font-weight:bold' lang=en-US>Body child &#129408; é</span><span | 3 | style='font-weight:bold' lang=en-US>Body trailing &#129408; é</span><span |
| 4 | lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> | 4 | lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/page-lifecycle/creation/native/cold/read/page-005.xml+1-1| ... | @@ -1,2 +1,2 @@ | ... | @@ -1,2 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{42FD4450-780C-0BA5-0961-013062918F74}{1}{B0}" name="New 🦋 é" dateTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:37:57.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:QuickStyleDef index="1" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Rust author" lastModifiedBy="Rust author" creationTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:22:39.000Z" objectID="{42FD4450-780C-0BA5-0961-013062918F74}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[New &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T04:33:45.000Z" objectID="{C18A5D2A-78C0-4E8A-BD22-A6FBC3361C82}{10}{B0}"><one:Position x="72.0" y="144.0" z="0"/><one:Size width="214.4557342529297" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T04:33:45.000Z" lastModifiedTime="2026-09-09T04:33:45.000Z" objectID="{C18A5D2A-78C0-4E8A-BD22-A6FBC3361C82}{11}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Native body 0 after Rust page creation.]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{953A9101-9B09-0272-0F34-667C003B8662}{1}{B0}" name="Untitled page" dateTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T06:56:14.000Z" pageLevel="1"><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings></one:Page> |
corpus/page-lifecycle/creation/native/cold/read/page-006.xml+1-1| ... | @@ -1,2 +1,2 @@ | ... | @@ -1,2 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{62E8EF5B-FE2A-0740-02BE-F3458C1B252E}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-09T04:37:57.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="17.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-09T03:09:19.000Z" objectID="{AC9D9E40-C29F-0F27-2939-A8751668A9D1}{15}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[]]></one:T></one:OE></one:Title></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{98E5398F-5E18-0F4C-2FB0-C1F2AA3311EA}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-26T06:56:13.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="17.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-09T03:09:19.000Z" objectID="{56904894-62AD-072B-0437-9AC230409D15}{15}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[]]></one:T></one:OE></one:Title></one:Page> |
corpus/page-lifecycle/creation/native/cold/read/page-007.xml+3-1| ... | @@ -1,2 +1,4 @@ | ... | @@ -1,2 +1,4 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{7C13E245-E47A-074C-28EC-9AE2078BEB7B}{1}{B0}" name="Untitled page" dateTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:37:57.000Z" pageLevel="1"><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{BBC7E3A5-47E8-4EAC-A880-774F406F5988}{1}{B0}" name="child" dateTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-26T06:56:13.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-09T03:09:29.000Z" objectID="{319396D6-A5C5-4A04-80C7-681DFBD4802C}{28}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[child]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{319396D6-A5C5-4A04-80C7-681DFBD4802C}{35}{B0}"><one:Position x="36.0" y="86.4000015258789" z="0"/><one:Size width="149.6102294921875" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-09T03:09:29.000Z" objectID="{319396D6-A5C5-4A04-80C7-681DFBD4802C}{36}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span |
| 3 | style='font-weight:bold' lang=en-US>Body child &#129408; é</span><span | ||
| 4 | lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/page-lifecycle/creation/native/cold/read/page-008.xml+1-1| ... | @@ -1,2 +1,2 @@ | ... | @@ -1,2 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{7D46D9FF-FA67-0B14-37A6-33F9D45D4EA0}{1}{B0}" name="Untitled page" dateTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:37:57.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Rust author" lastModifiedBy="Rust author" creationTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:22:39.000Z" objectID="{7D46D9FF-FA67-0B14-37A6-33F9D45D4EA0}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[]]></one:T></one:OE></one:Title></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{D288407C-CEB3-06DE-3BF3-C1D347A3DAC4}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-26T06:56:13.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="17.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-09T03:09:19.000Z" objectID="{FD6C85A7-61E6-0AC7-1099-B96D6BC50446}{15}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[]]></one:T></one:OE></one:Title></one:Page> |
corpus/page-lifecycle/creation/native/cold/read/page-009.xml+3-1| ... | @@ -1,2 +1,4 @@ | ... | @@ -1,2 +1,4 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{823CBA4A-EE9A-02FD-000F-7E46D4185611}{1}{B0}" name="New 🦋 é" dateTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:37:57.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:QuickStyleDef index="1" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Rust author" lastModifiedBy="Rust author" creationTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:22:39.000Z" objectID="{823CBA4A-EE9A-02FD-000F-7E46D4185611}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[New &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T04:33:45.000Z" objectID="{9BC368D2-94C7-42D5-8796-7FCAFD442299}{10}{B0}"><one:Position x="72.0" y="144.0" z="0"/><one:Size width="214.4557342529297" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T04:33:45.000Z" lastModifiedTime="2026-09-09T04:33:45.000Z" objectID="{9BC368D2-94C7-42D5-8796-7FCAFD442299}{11}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Native body 1 after Rust page creation.]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{E61B2166-DD22-40F3-B4A6-AFE36ADD055E}{1}{B0}" name="grandchild" dateTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-26T06:56:13.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{76CE0AD5-DC18-435C-88A3-1F6B4355661F}{32}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[grandchild]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{76CE0AD5-DC18-435C-88A3-1F6B4355661F}{39}{B0}"><one:Position x="36.0" y="86.4000015258789" z="0"/><one:Size width="175.9715728759766" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{76CE0AD5-DC18-435C-88A3-1F6B4355661F}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span |
| 3 | style='font-weight:bold' lang=en-US>Body grandchild &#129408; é</span><span | ||
| 4 | lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/page-lifecycle/creation/native/cold/read/page-010.xml+2-2| ... | @@ -1,4 +1,4 @@ | ... | @@ -1,4 +1,4 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{A430FF3F-9860-41C3-9AAF-33350CBB48CC}{1}{B0}" name="Body trailing 🦀 é preserved" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T04:37:57.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-09T03:09:34.000Z" lastModifiedTime="2026-09-09T03:09:34.000Z" objectID="{E1BEEEB2-952D-4471-8F7E-8F3540A7587C}{16}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{3501DF42-B7F0-4BF3-A8FA-51D12A3CA280}{39}{B0}"><one:Position x="36.0" y="86.4000015258789" z="0"/><one:Size width="160.1752014160156" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{3501DF42-B7F0-4BF3-A8FA-51D12A3CA280}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{E64BC836-C1AE-44AA-966D-4F9B0445CD79}{1}{B0}" name="Same title" dateTime="2026-09-09T03:09:27.000Z" lastModifiedTime="2026-09-26T06:56:13.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-09T03:09:27.000Z" lastModifiedTime="2026-09-09T03:09:27.000Z" objectID="{E450514C-9508-4E3F-A452-85942ABED3C4}{32}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:29.000Z" objectID="{E450514C-9508-4E3F-A452-85942ABED3C4}{39}{B0}"><one:Position x="36.0" y="86.4000015258789" z="0"/><one:Size width="158.2952728271484" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:27.000Z" lastModifiedTime="2026-09-09T03:09:29.000Z" objectID="{E450514C-9508-4E3F-A452-85942ABED3C4}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span |
| 3 | style='font-weight:bold' lang=en-US>Body trailing &#129408; é</span><span | 3 | style='font-weight:bold' lang=en-US>Body parent &#129408; é</span><span |
| 4 | lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> | 4 | lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/page-lifecycle/creation/native/cold/read/page-011.xml+1-3| ... | @@ -1,4 +1,2 @@ | ... | @@ -1,4 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{CFD5059F-AE9C-4BF2-A50F-316E9B82DCD6}{1}{B0}" name="Renamed 🦋 é" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T04:37:57.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-09T03:09:34.000Z" lastModifiedTime="2026-09-09T03:09:34.000Z" objectID="{E22DE42B-4827-4C82-B371-D12C4DD459A9}{16}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Renamed &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{A95ACF81-201A-4018-B631-4E4CEEB4E897}{39}{B0}"><one:Position x="36.0" y="86.4000015258789" z="0"/><one:Size width="170.2942504882812" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{A95ACF81-201A-4018-B631-4E4CEEB4E897}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{F287814F-C76B-01FC-2B6E-0727BC11F2A1}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-26T06:56:13.000Z" pageLevel="1"><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings></one:Page> |
| 3 | style='font-weight:bold' lang=en-US>Body duplicate &#129408; é</span><span | ||
| 4 | lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/page-lifecycle/creation/native/cold/read/page-012.xml+1-3| ... | @@ -1,4 +1,2 @@ | ... | @@ -1,4 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{DF799BDC-CD69-46A1-B115-5B34087265FD}{1}{B0}" name="Body automatic 🦀 é preserved" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T04:37:57.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{6807E194-65C3-4940-B3D7-800A2B2CC6EE}{14}{B0}"><one:Position x="36.0" y="14.40000057220459" z="0"/><one:Size width="174.3547973632812" height="13.42771625518799"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{6807E194-65C3-4940-B3D7-800A2B2CC6EE}{15}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{FD26FDEC-C6A0-013C-0700-36482F04C73B}{1}{B0}" name="Untitled page" dateTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T06:56:13.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Rust author" lastModifiedBy="Rust author" creationTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T06:32:12.000Z" objectID="{FD26FDEC-C6A0-013C-0700-36482F04C73B}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[]]></one:T></one:OE></one:Title></one:Page> |
| 3 | style='font-weight:bold' lang=en-US>Body automatic &#129408; é</span><span | ||
| 4 | lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/page-lifecycle/creation/native/notebook/Lifecycle.one| Binary files a/corpus/page-lifecycle/creation/native/notebook/Lifecycle.one and b/corpus/page-lifecycle/creation/native/notebook/Lifecycle.one differ | |||
corpus/page-lifecycle/creation/native/read/hierarchy.xml+1-1| ... | @@ -1,2 +1,2 @@ | ... | @@ -1,2 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Notebook xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" name="notebook" nickname="notebook" ID="{477DD477-3150-478E-84D1-0D30A0CB781D}{1}{B0}" path="C:\one-tests\runs\capture\notebook" lastModifiedTime="2026-09-09T04:35:14.000Z" color="#B49EDE"><one:Section name="Lifecycle" ID="{2E7A04FF-1560-053C-194A-E3E92C86F3EF}{1}{B0}" path="C:\one-tests\runs\capture\notebook\Lifecycle.one" lastModifiedTime="2026-09-09T04:35:14.000Z" color="#91BAAE"><one:Page ID="{D1EE6BAE-5EAD-016F-394F-B38DAD8ABAF1}{1}{B0}" name="Untitled page" dateTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:33:41.000Z" pageLevel="1"/><one:Page ID="{CE405D0A-5AE0-0D3B-0C57-7331F5CCD17F}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-09T04:33:41.000Z" pageLevel="1"/><one:Page ID="{842D24F9-CA4B-04A9-1814-7310185C1A51}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-09T04:33:41.000Z" pageLevel="1"/><one:Page ID="{A422E5CA-C393-038B-0889-B5E4E3EE3234}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-09T04:33:41.000Z" pageLevel="1"/><one:Page ID="{B0EEACB3-C556-46DD-B58A-FD585BBA0DEC}{1}{B0}" name="Same title" dateTime="2026-09-09T03:09:27.000Z" lastModifiedTime="2026-09-09T04:33:41.000Z" pageLevel="1"/><one:Page ID="{ED628720-4310-4CDB-8B67-C58C1F90991D}{1}{B0}" name="child" dateTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-09T04:33:41.000Z" pageLevel="1"/><one:Page ID="{B0BE45E3-D9DA-4284-9741-1D203522C5CB}{1}{B0}" name="grandchild" dateTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-09T04:33:41.000Z" pageLevel="1"/><one:Page ID="{637DB7CE-0A56-4189-ABE6-B11AE2552887}{1}{B0}" name="Renamed 🦋 é" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T04:33:42.000Z" pageLevel="1"/><one:Page ID="{08984D6E-3CAA-4BB8-9446-B341756CBC9D}{1}{B0}" name="Body trailing 🦀 é preserved" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T04:33:42.000Z" pageLevel="1"/><one:Page ID="{73D1298D-69A3-4CDA-BFFC-DB4071A591AC}{1}{B0}" name="Body automatic 🦀 é preserved" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T04:33:42.000Z" pageLevel="1"/><one:Page ID="{D0BB5014-40B0-0D37-2605-1A967E5C1F2A}{1}{B0}" name="Untitled page" dateTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:33:42.000Z" pageLevel="1"/><one:Page ID="{EE55F601-DCC6-01DE-0788-81441B467B25}{1}{B0}" name="New 🦋 é" dateTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:33:45.000Z" pageLevel="1"/><one:Page ID="{2E94081B-4A50-0886-0EE6-FE32ADCFA240}{1}{B0}" name="New 🦋 é" dateTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:33:45.000Z" pageLevel="1"/></one:Section></one:Notebook> | 2 | <one:Notebook xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" name="notebook" nickname="notebook" ID="{F0EF8762-C1B7-43BB-AF98-F0B81C916A40}{1}{B0}" path="C:\one-tests\runs\capture\notebook" lastModifiedTime="2026-09-26T06:52:30.000Z" color="#B49EDE"><one:Section name="Lifecycle" ID="{5F0C301D-2DB7-0FF8-0A4D-AB589AADC2FA}{1}{B0}" path="C:\one-tests\runs\capture\notebook\Lifecycle.one" lastModifiedTime="2026-09-26T06:52:30.000Z" color="#91BAAE"><one:Page ID="{DAF5AD8B-FA8F-098F-37E0-CC3AC6D036BB}{1}{B0}" name="Untitled page" dateTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T06:51:01.000Z" pageLevel="1"/><one:Page ID="{BF3669E8-6237-07FF-1F50-3B8043E7E06A}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-26T06:51:01.000Z" pageLevel="1"/><one:Page ID="{F55B101B-F29C-0E6D-0B13-3BA1AE772B44}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-26T06:51:01.000Z" pageLevel="1"/><one:Page ID="{D554D128-FB44-094F-1B8E-FD5555C50321}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-26T06:51:01.000Z" pageLevel="1"/><one:Page ID="{C1989851-FD81-4C19-A68D-B5E9ED913CF9}{1}{B0}" name="Same title" dateTime="2026-09-09T03:09:27.000Z" lastModifiedTime="2026-09-26T06:51:01.000Z" pageLevel="1"/><one:Page ID="{9C14B3C2-7BC7-461F-9860-8D3DA9BBA808}{1}{B0}" name="child" dateTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-26T06:51:01.000Z" pageLevel="1"/><one:Page ID="{C1C87101-E10D-4840-8446-55918309F4DE}{1}{B0}" name="grandchild" dateTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-26T06:51:01.000Z" pageLevel="1"/><one:Page ID="{120B832C-3281-4B4D-B8E1-F9AB547E1992}{1}{B0}" name="Renamed 🦋 é" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-26T06:51:01.000Z" pageLevel="1"/><one:Page ID="{79EE798C-047D-417C-8741-FBF0C3478D88}{1}{B0}" name="Body trailing 🦀 é preserved" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-26T06:51:01.000Z" pageLevel="1"/><one:Page ID="{02A71D6F-5174-461E-ACFB-93F1C78EA0B9}{1}{B0}" name="Body automatic 🦀 é preserved" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-26T06:51:01.000Z" pageLevel="1"/><one:Page ID="{B2E9C166-A726-0AC1-3FD4-9C0EE9EF77E2}{1}{B0}" name="Untitled page" dateTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T06:51:01.000Z" pageLevel="1"/><one:Page ID="{712D8B34-0941-0E04-18FE-DB671B9E62A4}{1}{B0}" name="New 🦋 é" dateTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T06:51:06.000Z" pageLevel="1"/><one:Page ID="{3F28BB2F-40E9-07A3-26D0-DD9CA47A6D5C}{1}{B0}" name="New 🦋 é" dateTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T06:51:06.000Z" pageLevel="1"/></one:Section></one:Notebook> |
corpus/page-lifecycle/creation/native/read/page-000.xml+2-2| ... | @@ -1,4 +1,4 @@ | ... | @@ -1,4 +1,4 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{08984D6E-3CAA-4BB8-9446-B341756CBC9D}{1}{B0}" name="Body trailing 🦀 é preserved" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T04:33:42.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-09T03:09:34.000Z" lastModifiedTime="2026-09-09T03:09:34.000Z" objectID="{4D165CE3-31E7-4E0A-8197-0F413970AC2D}{16}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{99A96D13-133A-4188-A613-D1A553EB56D1}{39}{B0}"><one:Position x="36.0" y="86.4000015258789" z="0"/><one:Size width="160.1752014160156" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{99A96D13-133A-4188-A613-D1A553EB56D1}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{02A71D6F-5174-461E-ACFB-93F1C78EA0B9}{1}{B0}" name="Body automatic 🦀 é preserved" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-26T06:51:01.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{B5D96727-F9DE-49FF-AE39-48CFE4D003AA}{14}{B0}"><one:Position x="36.0" y="14.40000057220459" z="0"/><one:Size width="174.3547973632812" height="13.42771625518799"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{B5D96727-F9DE-49FF-AE39-48CFE4D003AA}{15}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span |
| 3 | style='font-weight:bold' lang=en-US>Body trailing &#129408; é</span><span | 3 | style='font-weight:bold' lang=en-US>Body automatic &#129408; é</span><span |
| 4 | lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> | 4 | lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/page-lifecycle/creation/native/read/page-001.xml+3-1| ... | @@ -1,2 +1,4 @@ | ... | @@ -1,2 +1,4 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{2E94081B-4A50-0886-0EE6-FE32ADCFA240}{1}{B0}" name="New 🦋 é" dateTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:33:45.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:QuickStyleDef index="1" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Rust author" lastModifiedBy="Rust author" creationTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:22:39.000Z" objectID="{2E94081B-4A50-0886-0EE6-FE32ADCFA240}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[New &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T04:33:45.000Z" objectID="{376BDA83-300D-48AE-897F-FFBE8493D6C8}{10}{B0}"><one:Position x="72.0" y="144.0" z="0"/><one:Size width="214.4557342529297" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T04:33:45.000Z" lastModifiedTime="2026-09-09T04:33:45.000Z" objectID="{376BDA83-300D-48AE-897F-FFBE8493D6C8}{11}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Native body 1 after Rust page creation.]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{120B832C-3281-4B4D-B8E1-F9AB547E1992}{1}{B0}" name="Renamed 🦋 é" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-26T06:51:01.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-09T03:09:34.000Z" lastModifiedTime="2026-09-09T03:09:34.000Z" objectID="{3FF36298-D43A-4C3D-AE9F-19E982289CED}{16}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Renamed &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{74844932-BC07-40A7-ABDF-868921482DD3}{39}{B0}"><one:Position x="36.0" y="86.4000015258789" z="0"/><one:Size width="170.2942504882812" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{74844932-BC07-40A7-ABDF-868921482DD3}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span |
| 3 | style='font-weight:bold' lang=en-US>Body duplicate &#129408; é</span><span | ||
| 4 | lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/page-lifecycle/creation/native/read/page-002.xml+1-3| ... | @@ -1,4 +1,2 @@ | ... | @@ -1,4 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{637DB7CE-0A56-4189-ABE6-B11AE2552887}{1}{B0}" name="Renamed 🦋 é" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T04:33:42.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-09T03:09:34.000Z" lastModifiedTime="2026-09-09T03:09:34.000Z" objectID="{4E85567A-ECED-46F9-BD98-51583403ADF8}{16}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Renamed &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{05F27DD0-84D0-4A63-B8D8-CE3897631CC6}{39}{B0}"><one:Position x="36.0" y="86.4000015258789" z="0"/><one:Size width="170.2942504882812" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{05F27DD0-84D0-4A63-B8D8-CE3897631CC6}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{3F28BB2F-40E9-07A3-26D0-DD9CA47A6D5C}{1}{B0}" name="New 🦋 é" dateTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T06:51:06.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:QuickStyleDef index="1" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Rust author" lastModifiedBy="Rust author" creationTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T06:32:12.000Z" objectID="{3F28BB2F-40E9-07A3-26D0-DD9CA47A6D5C}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[New &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-26T06:51:06.000Z" objectID="{6A4B4FCC-D219-4115-9E30-FC2C56B1A8B9}{10}{B0}"><one:Position x="72.0" y="144.0" z="0"/><one:Size width="214.4557342529297" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-26T06:51:05.000Z" lastModifiedTime="2026-09-26T06:51:05.000Z" objectID="{6A4B4FCC-D219-4115-9E30-FC2C56B1A8B9}{11}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Native body 1 after Rust page creation.]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
| 3 | style='font-weight:bold' lang=en-US>Body duplicate &#129408; é</span><span | ||
| 4 | lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/page-lifecycle/creation/native/read/page-003.xml+1-3| ... | @@ -1,4 +1,2 @@ | ... | @@ -1,4 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{73D1298D-69A3-4CDA-BFFC-DB4071A591AC}{1}{B0}" name="Body automatic 🦀 é preserved" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T04:33:42.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{C4AF53C5-C109-433B-BD3E-007E52FB32BF}{14}{B0}"><one:Position x="36.0" y="14.40000057220459" z="0"/><one:Size width="174.3547973632812" height="13.42771625518799"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{C4AF53C5-C109-433B-BD3E-007E52FB32BF}{15}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{712D8B34-0941-0E04-18FE-DB671B9E62A4}{1}{B0}" name="New 🦋 é" dateTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T06:51:06.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:QuickStyleDef index="1" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Rust author" lastModifiedBy="Rust author" creationTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T06:32:12.000Z" objectID="{712D8B34-0941-0E04-18FE-DB671B9E62A4}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[New &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-26T06:51:05.000Z" objectID="{04957741-CE26-4172-A8B2-0F41A8BABFF8}{10}{B0}"><one:Position x="72.0" y="144.0" z="0"/><one:Size width="214.4557342529297" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-26T06:51:05.000Z" lastModifiedTime="2026-09-26T06:51:05.000Z" objectID="{04957741-CE26-4172-A8B2-0F41A8BABFF8}{11}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Native body 0 after Rust page creation.]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
| 3 | style='font-weight:bold' lang=en-US>Body automatic &#129408; é</span><span | ||
| 4 | lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/page-lifecycle/creation/native/read/page-004.xml+3-1| ... | @@ -1,2 +1,4 @@ | ... | @@ -1,2 +1,4 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{842D24F9-CA4B-04A9-1814-7310185C1A51}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-09T04:33:41.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="17.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-09T03:09:19.000Z" objectID="{ABC9E122-651E-08B0-337E-0BAE343AC4D3}{15}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[]]></one:T></one:OE></one:Title></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{79EE798C-047D-417C-8741-FBF0C3478D88}{1}{B0}" name="Body trailing 🦀 é preserved" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-26T06:51:01.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-09T03:09:34.000Z" lastModifiedTime="2026-09-09T03:09:34.000Z" objectID="{3C606801-0930-44CE-9290-47F08F5B9D38}{16}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{E8DF59F1-2BED-4B4C-B514-9914E5C067C4}{39}{B0}"><one:Position x="36.0" y="86.4000015258789" z="0"/><one:Size width="160.1752014160156" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{E8DF59F1-2BED-4B4C-B514-9914E5C067C4}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span |
| 3 | style='font-weight:bold' lang=en-US>Body trailing &#129408; é</span><span | ||
| 4 | lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/page-lifecycle/creation/native/read/page-005.xml+3-1| ... | @@ -1,2 +1,4 @@ | ... | @@ -1,2 +1,4 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{A422E5CA-C393-038B-0889-B5E4E3EE3234}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-09T04:33:41.000Z" pageLevel="1"><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{9C14B3C2-7BC7-461F-9860-8D3DA9BBA808}{1}{B0}" name="child" dateTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-26T06:51:01.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-09T03:09:29.000Z" objectID="{1640C6B1-99EA-42B7-B027-926F120071AC}{28}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[child]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{1640C6B1-99EA-42B7-B027-926F120071AC}{35}{B0}"><one:Position x="36.0" y="86.4000015258789" z="0"/><one:Size width="149.6102294921875" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-09T03:09:29.000Z" objectID="{1640C6B1-99EA-42B7-B027-926F120071AC}{36}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span |
| 3 | style='font-weight:bold' lang=en-US>Body child &#129408; é</span><span | ||
| 4 | lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/page-lifecycle/creation/native/read/page-006.xml+1-3| ... | @@ -1,4 +1,2 @@ | ... | @@ -1,4 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{B0BE45E3-D9DA-4284-9741-1D203522C5CB}{1}{B0}" name="grandchild" dateTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-09T04:33:41.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{206B6E50-D8E0-412B-AB44-ADA81CAAA68A}{32}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[grandchild]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{206B6E50-D8E0-412B-AB44-ADA81CAAA68A}{39}{B0}"><one:Position x="36.0" y="86.4000015258789" z="0"/><one:Size width="175.9715728759766" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{206B6E50-D8E0-412B-AB44-ADA81CAAA68A}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{B2E9C166-A726-0AC1-3FD4-9C0EE9EF77E2}{1}{B0}" name="Untitled page" dateTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T06:51:01.000Z" pageLevel="1"><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings></one:Page> |
| 3 | style='font-weight:bold' lang=en-US>Body grandchild &#129408; é</span><span | ||
| 4 | lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/page-lifecycle/creation/native/read/page-007.xml+1-3| ... | @@ -1,4 +1,2 @@ | ... | @@ -1,4 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{B0EEACB3-C556-46DD-B58A-FD585BBA0DEC}{1}{B0}" name="Same title" dateTime="2026-09-09T03:09:27.000Z" lastModifiedTime="2026-09-09T04:33:41.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-09T03:09:27.000Z" lastModifiedTime="2026-09-09T03:09:27.000Z" objectID="{B2F535C9-91F0-4C48-87B5-375775411351}{32}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:29.000Z" objectID="{B2F535C9-91F0-4C48-87B5-375775411351}{39}{B0}"><one:Position x="36.0" y="86.4000015258789" z="0"/><one:Size width="158.2952728271484" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:27.000Z" lastModifiedTime="2026-09-09T03:09:29.000Z" objectID="{B2F535C9-91F0-4C48-87B5-375775411351}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{BF3669E8-6237-07FF-1F50-3B8043E7E06A}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-26T06:51:01.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="17.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-09T03:09:19.000Z" objectID="{714318F3-5E82-0F98-34D7-60B0D9946C95}{15}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[]]></one:T></one:OE></one:Title></one:Page> |
| 3 | style='font-weight:bold' lang=en-US>Body parent &#129408; é</span><span | ||
| 4 | lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/page-lifecycle/creation/native/read/page-008.xml+3-1| ... | @@ -1,2 +1,4 @@ | ... | @@ -1,2 +1,4 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{CE405D0A-5AE0-0D3B-0C57-7331F5CCD17F}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-09T04:33:41.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="17.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-09T03:09:19.000Z" objectID="{00352C11-6655-055C-27D0-28016FBF5D80}{15}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[]]></one:T></one:OE></one:Title></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{C1989851-FD81-4C19-A68D-B5E9ED913CF9}{1}{B0}" name="Same title" dateTime="2026-09-09T03:09:27.000Z" lastModifiedTime="2026-09-26T06:51:01.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-09T03:09:27.000Z" lastModifiedTime="2026-09-09T03:09:27.000Z" objectID="{C383012B-A927-468C-94B2-7FE6C36A2244}{32}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:29.000Z" objectID="{C383012B-A927-468C-94B2-7FE6C36A2244}{39}{B0}"><one:Position x="36.0" y="86.4000015258789" z="0"/><one:Size width="158.2952728271484" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:27.000Z" lastModifiedTime="2026-09-09T03:09:29.000Z" objectID="{C383012B-A927-468C-94B2-7FE6C36A2244}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span |
| 3 | style='font-weight:bold' lang=en-US>Body parent &#129408; é</span><span | ||
| 4 | lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/page-lifecycle/creation/native/read/page-009.xml+3-1| ... | @@ -1,2 +1,4 @@ | ... | @@ -1,2 +1,4 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{D0BB5014-40B0-0D37-2605-1A967E5C1F2A}{1}{B0}" name="Untitled page" dateTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:33:42.000Z" pageLevel="1"><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{C1C87101-E10D-4840-8446-55918309F4DE}{1}{B0}" name="grandchild" dateTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-26T06:51:01.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{511D5AB2-E037-4BEF-B843-E519AA81979F}{32}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[grandchild]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{511D5AB2-E037-4BEF-B843-E519AA81979F}{39}{B0}"><one:Position x="36.0" y="86.4000015258789" z="0"/><one:Size width="175.9715728759766" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{511D5AB2-E037-4BEF-B843-E519AA81979F}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span |
| 3 | style='font-weight:bold' lang=en-US>Body grandchild &#129408; é</span><span | ||
| 4 | lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/page-lifecycle/creation/native/read/page-010.xml+1-1| ... | @@ -1,2 +1,2 @@ | ... | @@ -1,2 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{D1EE6BAE-5EAD-016F-394F-B38DAD8ABAF1}{1}{B0}" name="Untitled page" dateTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:33:41.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Rust author" lastModifiedBy="Rust author" creationTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:22:39.000Z" objectID="{D1EE6BAE-5EAD-016F-394F-B38DAD8ABAF1}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[]]></one:T></one:OE></one:Title></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{D554D128-FB44-094F-1B8E-FD5555C50321}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-26T06:51:01.000Z" pageLevel="1"><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings></one:Page> |
corpus/page-lifecycle/creation/native/read/page-011.xml+1-3| ... | @@ -1,4 +1,2 @@ | ... | @@ -1,4 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{ED628720-4310-4CDB-8B67-C58C1F90991D}{1}{B0}" name="child" dateTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-09T04:33:41.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-09T03:09:29.000Z" objectID="{6736F253-A13D-4873-A320-DADEA42B40B9}{28}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[child]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{6736F253-A13D-4873-A320-DADEA42B40B9}{35}{B0}"><one:Position x="36.0" y="86.4000015258789" z="0"/><one:Size width="149.6102294921875" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-09T03:09:29.000Z" objectID="{6736F253-A13D-4873-A320-DADEA42B40B9}{36}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{DAF5AD8B-FA8F-098F-37E0-CC3AC6D036BB}{1}{B0}" name="Untitled page" dateTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T06:51:01.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Rust author" lastModifiedBy="Rust author" creationTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T06:32:12.000Z" objectID="{DAF5AD8B-FA8F-098F-37E0-CC3AC6D036BB}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[]]></one:T></one:OE></one:Title></one:Page> |
| 3 | style='font-weight:bold' lang=en-US>Body child &#129408; é</span><span | ||
| 4 | lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/page-lifecycle/creation/native/read/page-012.xml+1-1| ... | @@ -1,2 +1,2 @@ | ... | @@ -1,2 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{EE55F601-DCC6-01DE-0788-81441B467B25}{1}{B0}" name="New 🦋 é" dateTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:33:45.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:QuickStyleDef index="1" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Rust author" lastModifiedBy="Rust author" creationTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:22:39.000Z" objectID="{EE55F601-DCC6-01DE-0788-81441B467B25}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[New &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T04:33:45.000Z" objectID="{6D22EF7B-DC0A-44F1-B3CB-268FBAE1E8D3}{10}{B0}"><one:Position x="72.0" y="144.0" z="0"/><one:Size width="214.4557342529297" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T04:33:45.000Z" lastModifiedTime="2026-09-09T04:33:45.000Z" objectID="{6D22EF7B-DC0A-44F1-B3CB-268FBAE1E8D3}{11}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Native body 0 after Rust page creation.]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{F55B101B-F29C-0E6D-0B13-3BA1AE772B44}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-26T06:51:01.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="17.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-09T03:09:19.000Z" objectID="{DABFD5C0-5DC9-0274-2079-431F8211F5C6}{15}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[]]></one:T></one:OE></one:Title></one:Page> |
corpus/page-lifecycle/creation/provenance.json+16-16| ... | @@ -1,8 +1,8 @@ | ... | @@ -1,8 +1,8 @@ |
| 1 | { | 1 | { |
| 2 | "cold": { | 2 | "cold": { |
| 3 | "capture": "evidence/m10/page-create-cold", | 3 | "capture": "evidence/titles/cold-creation", |
| 4 | "run": { | 4 | "run": { |
| 5 | "notebook": "/Users/clo/dev/one/evidence/m10/page-create-candidate", | 5 | "notebook": "/Users/clo/dev/one/evidence/titles/creation", |
| 6 | "expected_pages": 13, | 6 | "expected_pages": 13, |
| 7 | "author": null, | 7 | "author": null, |
| 8 | "author_timeout_seconds": 600, | 8 | "author_timeout_seconds": 600, |
| ... | @@ -20,17 +20,17 @@ | ... | @@ -20,17 +20,17 @@ |
| 20 | } | 20 | } |
| 21 | }, | 21 | }, |
| 22 | "machine": { | 22 | "machine": { |
| 23 | "name": "m6-4b25125e", | 23 | "name": "m6-5472ef31", |
| 24 | "hostname": "ONE-M6-4B25125E" | 24 | "hostname": "ONE-M6-5472EF31" |
| 25 | }, | 25 | }, |
| 26 | "teardown": { | 26 | "teardown": { |
| 27 | "absent": true | 27 | "absent": true |
| 28 | } | 28 | } |
| 29 | }, | 29 | }, |
| 30 | "native": { | 30 | "native": { |
| 31 | "capture": "evidence/m10/page-create-native-edit", | 31 | "capture": "evidence/titles/native", |
| 32 | "run": { | 32 | "run": { |
| 33 | "notebook": "/Users/clo/dev/one/evidence/m10/page-create-cold/notebook", | 33 | "notebook": "/Users/clo/dev/one/evidence/titles/native-in", |
| 34 | "expected_pages": 13, | 34 | "expected_pages": 13, |
| 35 | "author": "tools/native/created-pages.ps1", | 35 | "author": "tools/native/created-pages.ps1", |
| 36 | "author_timeout_seconds": 600, | 36 | "author_timeout_seconds": 600, |
| ... | @@ -49,17 +49,17 @@ | ... | @@ -49,17 +49,17 @@ |
| 49 | } | 49 | } |
| 50 | }, | 50 | }, |
| 51 | "machine": { | 51 | "machine": { |
| 52 | "name": "m6-71dafa89", | 52 | "name": "m6-ba7e55f4", |
| 53 | "hostname": "ONE-M6-71DAFA89" | 53 | "hostname": "ONE-M6-BA7E55F4" |
| 54 | }, | 54 | }, |
| 55 | "teardown": { | 55 | "teardown": { |
| 56 | "absent": true | 56 | "absent": true |
| 57 | } | 57 | } |
| 58 | }, | 58 | }, |
| 59 | "native/cold": { | 59 | "native/cold": { |
| 60 | "capture": "evidence/m10/page-create-native-edit-cold", | 60 | "capture": "evidence/titles/native-cold", |
| 61 | "run": { | 61 | "run": { |
| 62 | "notebook": "/Users/clo/dev/one/evidence/m10/page-create-native-edit/notebook", | 62 | "notebook": "/Users/clo/dev/one/evidence/titles/native/notebook", |
| 63 | "expected_pages": 13, | 63 | "expected_pages": 13, |
| 64 | "author": null, | 64 | "author": null, |
| 65 | "author_timeout_seconds": 600, | 65 | "author_timeout_seconds": 600, |
| ... | @@ -77,17 +77,17 @@ | ... | @@ -77,17 +77,17 @@ |
| 77 | } | 77 | } |
| 78 | }, | 78 | }, |
| 79 | "machine": { | 79 | "machine": { |
| 80 | "name": "m6-eabc83c1", | 80 | "name": "m6-426eea56", |
| 81 | "hostname": "ONE-M6-EABC83C1" | 81 | "hostname": "ONE-M6-426EEA56" |
| 82 | }, | 82 | }, |
| 83 | "teardown": { | 83 | "teardown": { |
| 84 | "absent": true | 84 | "absent": true |
| 85 | } | 85 | } |
| 86 | }, | 86 | }, |
| 87 | "followup/cold": { | 87 | "followup/cold": { |
| 88 | "capture": "evidence/m10/page-create-rust-followup-cold", | 88 | "capture": "evidence/titles/followup-cold", |
| 89 | "run": { | 89 | "run": { |
| 90 | "notebook": "/Users/clo/dev/one/evidence/m10/page-create-rust-followup", | 90 | "notebook": "/Users/clo/dev/one/evidence/titles/followup", |
| 91 | "expected_pages": 13, | 91 | "expected_pages": 13, |
| 92 | "author": null, | 92 | "author": null, |
| 93 | "author_timeout_seconds": 600, | 93 | "author_timeout_seconds": 600, |
| ... | @@ -105,8 +105,8 @@ | ... | @@ -105,8 +105,8 @@ |
| 105 | } | 105 | } |
| 106 | }, | 106 | }, |
| 107 | "machine": { | 107 | "machine": { |
| 108 | "name": "m6-5a91d9d1", | 108 | "name": "m6-44be46f3", |
| 109 | "hostname": "ONE-M6-5A91D9D1" | 109 | "hostname": "ONE-M6-44BE46F3" |
| 110 | }, | 110 | }, |
| 111 | "teardown": { | 111 | "teardown": { |
| 112 | "absent": true | 112 | "absent": true |
corpus/page-lifecycle/offline-creation/candidate/pages.one| Binary files a/corpus/page-lifecycle/offline-creation/candidate/pages.one and b/corpus/page-lifecycle/offline-creation/candidate/pages.one differ | |||
corpus/page-lifecycle/offline-creation/cold/notebook/pages.one| Binary files a/corpus/page-lifecycle/offline-creation/cold/notebook/pages.one and b/corpus/page-lifecycle/offline-creation/cold/notebook/pages.one differ | |||
corpus/page-lifecycle/offline-creation/cold/read/hierarchy.xml+1-1| ... | @@ -1,2 +1,2 @@ | ... | @@ -1,2 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Notebook xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" name="notebook" nickname="notebook" ID="{F830A298-FD46-4884-B4C0-6F0BF45C3B90}{1}{B0}" path="C:\one-tests\runs\capture\notebook" lastModifiedTime="2026-09-09T05:08:15.000Z" color="#B49EDE"><one:Section name="pages" ID="{9ADFA36E-F0EB-051E-115A-102FF772CA03}{1}{B0}" path="C:\one-tests\runs\capture\notebook\pages.one" lastModifiedTime="2026-09-09T05:08:15.000Z" color="#8AA8E4"><one:Page ID="{1F0CC9C0-C49B-0C91-08C6-AA89176EF5BF}{1}{B0}" name="Original" dateTime="2026-09-09T05:06:23.000Z" lastModifiedTime="2026-09-09T05:08:15.000Z" pageLevel="1"/><one:Page ID="{D3E5FB89-79C3-05BA-1805-E25CE31ED175}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-09T05:06:24.000Z" lastModifiedTime="2026-09-09T05:08:15.000Z" pageLevel="1"/><one:Page ID="{AC84259B-D9B6-0A59-2748-93C35602B0BD}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-09T05:06:24.000Z" lastModifiedTime="2026-09-09T05:08:15.000Z" pageLevel="1"/><one:Page ID="{CC3DEC87-5707-084C-0528-649AB24AD8B7}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-09T05:06:24.000Z" lastModifiedTime="2026-09-09T05:08:15.000Z" pageLevel="1"/><one:Page ID="{7CA0430A-8905-04E4-0455-CFE53ECEE2AA}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-09T05:06:25.000Z" lastModifiedTime="2026-09-09T05:08:15.000Z" pageLevel="1"/><one:Page ID="{557FC918-74CB-09B0-19E7-3D1809D43E13}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-09T05:06:25.000Z" lastModifiedTime="2026-09-09T05:08:15.000Z" pageLevel="1"/><one:Page ID="{54F2D7D9-CAFE-0C6B-232B-2AE669F85BEA}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-09T05:06:25.000Z" lastModifiedTime="2026-09-09T05:08:15.000Z" pageLevel="1"/><one:Page ID="{9EE67EFB-DA69-05C0-334D-18E6DCBBE33B}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-09T05:06:25.000Z" lastModifiedTime="2026-09-09T05:08:15.000Z" pageLevel="1"/><one:Page ID="{52FFB786-C5E9-07F5-1BE1-C5095C3F3F9B}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-09T05:06:26.000Z" lastModifiedTime="2026-09-09T05:08:15.000Z" pageLevel="1"/><one:Page ID="{1606DF11-F2FE-0BF3-2AD6-DF46E0A08ECA}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-09T05:06:26.000Z" lastModifiedTime="2026-09-09T05:08:15.000Z" pageLevel="1"/><one:Page ID="{6CB51A17-61D4-0DBF-2C43-4CB4C9D2B6A1}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-09T05:06:26.000Z" lastModifiedTime="2026-09-09T05:08:15.000Z" pageLevel="1"/><one:Page ID="{87E24DEE-963B-09DA-1E68-D198DC52EDA5}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-09T05:06:26.000Z" lastModifiedTime="2026-09-09T05:08:15.000Z" pageLevel="1"/><one:Page ID="{AC1E7B39-43AC-0370-2960-486A10D01E43}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-09T05:06:27.000Z" lastModifiedTime="2026-09-09T05:08:15.000Z" pageLevel="1"/></one:Section></one:Notebook> | 2 | <one:Notebook xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" name="notebook" nickname="notebook" ID="{EDDEDB7B-13B7-4165-A533-B9C06F49385A}{1}{B0}" path="C:\one-tests\runs\capture\notebook" lastModifiedTime="2026-09-26T06:39:54.000Z" color="#B49EDE"><one:Section name="pages" ID="{3A067CBC-DC0D-05D8-02F3-B2A77833F748}{1}{B0}" path="C:\one-tests\runs\capture\notebook\pages.one" lastModifiedTime="2026-09-26T06:39:54.000Z" color="#8AA8E4"><one:Page ID="{2B1E474F-E067-06BF-29A3-033B4987A848}{1}{B0}" name="Original" dateTime="2026-09-26T06:33:06.000Z" lastModifiedTime="2026-09-26T06:39:44.000Z" pageLevel="1"/><one:Page ID="{849FA331-A262-0BFD-1424-6ABB50BCD8C7}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-26T06:33:06.000Z" lastModifiedTime="2026-09-26T06:39:44.000Z" pageLevel="1"/><one:Page ID="{91186C32-65B4-0578-030C-10DA1738633A}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-26T06:33:06.000Z" lastModifiedTime="2026-09-26T06:39:45.000Z" pageLevel="1"/><one:Page ID="{363E253C-E20B-038F-3B6E-740FF08C2966}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-26T06:33:07.000Z" lastModifiedTime="2026-09-26T06:39:45.000Z" pageLevel="1"/><one:Page ID="{DAF8F05F-4D73-0BFF-1A5C-60D6A0F48D49}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-26T06:33:07.000Z" lastModifiedTime="2026-09-26T06:39:45.000Z" pageLevel="1"/><one:Page ID="{6BD09F5F-AC47-06D8-3796-A163670C2EFD}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-26T06:33:07.000Z" lastModifiedTime="2026-09-26T06:39:45.000Z" pageLevel="1"/><one:Page ID="{06EF31AC-FE47-058C-07BA-DF78D1983F03}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-26T06:33:07.000Z" lastModifiedTime="2026-09-26T06:39:45.000Z" pageLevel="1"/><one:Page ID="{5BCF0FAC-7A3F-0144-15E4-A5B7CD35716B}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-26T06:33:07.000Z" lastModifiedTime="2026-09-26T06:39:45.000Z" pageLevel="1"/><one:Page ID="{8EE0AC66-288F-02EF-1E6D-FC56C315C68F}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-26T06:33:07.000Z" lastModifiedTime="2026-09-26T06:39:45.000Z" pageLevel="1"/><one:Page ID="{670CA118-DAE0-03FD-32C3-C0BBC132147E}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-26T06:33:08.000Z" lastModifiedTime="2026-09-26T06:39:45.000Z" pageLevel="1"/><one:Page ID="{535A6811-A498-0BA3-2BE5-52623B656182}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-26T06:33:08.000Z" lastModifiedTime="2026-09-26T06:39:45.000Z" pageLevel="1"/><one:Page ID="{CC15B979-9239-00D3-0F06-26192E32E6AE}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-26T06:33:08.000Z" lastModifiedTime="2026-09-26T06:39:45.000Z" pageLevel="1"/><one:Page ID="{3720123D-2FCD-0950-2E1C-389C62F8A52F}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-26T06:33:08.000Z" lastModifiedTime="2026-09-26T06:39:45.000Z" pageLevel="1"/></one:Section></one:Notebook> |
corpus/page-lifecycle/offline-creation/cold/read/page-000.xml+1-1| ... | @@ -1,2 +1,2 @@ | ... | @@ -1,2 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{1606DF11-F2FE-0BF3-2AD6-DF46E0A08ECA}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-09T05:06:26.000Z" lastModifiedTime="2026-09-09T05:08:15.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Offline author" lastModifiedBy="Offline author" creationTime="2026-09-09T05:06:26.000Z" lastModifiedTime="2026-09-09T05:06:26.000Z" objectID="{1606DF11-F2FE-0BF3-2AD6-DF46E0A08ECA}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-09T05:06:26.000Z" objectID="{4DC55091-E701-07D1-1F57-520E4B9C14AD}{1}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="97.35448455810547" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-09T05:06:26.000Z" lastModifiedTime="2026-09-09T05:06:26.000Z" objectID="{4DC55091-E701-07D1-1F57-520E4B9C14AD}{3}{B0}" alignment="left"><one:T><![CDATA[Body 8]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{06EF31AC-FE47-058C-07BA-DF78D1983F03}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-26T06:33:07.000Z" lastModifiedTime="2026-09-26T06:39:45.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Offline author" lastModifiedBy="Offline author" creationTime="2026-09-26T06:33:07.000Z" lastModifiedTime="2026-09-26T06:33:07.000Z" objectID="{06EF31AC-FE47-058C-07BA-DF78D1983F03}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="Offline author" lastModifiedBy="Offline author" lastModifiedTime="2026-09-26T06:33:07.000Z" objectID="{FB857531-64AC-0D81-2D4A-97656A27EA6B}{17}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="97.35448455810547" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-26T06:33:07.000Z" lastModifiedTime="2026-09-26T06:33:07.000Z" objectID="{FB857531-64AC-0D81-2D4A-97656A27EA6B}{18}{B0}" alignment="left"><one:T><![CDATA[Body 5]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/page-lifecycle/offline-creation/cold/read/page-001.xml+1-1| ... | @@ -1,2 +1,2 @@ | ... | @@ -1,2 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{1F0CC9C0-C49B-0C91-08C6-AA89176EF5BF}{1}{B0}" name="Original" dateTime="2026-09-09T05:06:23.000Z" lastModifiedTime="2026-09-09T05:08:15.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-09T05:06:23.000Z" objectID="{9ADFA36E-F0EB-051E-115A-102FF772CA03}{23}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="101.66748046875" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-09T05:06:23.000Z" lastModifiedTime="2026-09-09T05:06:23.000Z" objectID="{9ADFA36E-F0EB-051E-115A-102FF772CA03}{24}{B0}" alignment="left"><one:T><![CDATA[Original]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{2B1E474F-E067-06BF-29A3-033B4987A848}{1}{B0}" name="Original" dateTime="2026-09-26T06:33:06.000Z" lastModifiedTime="2026-09-26T06:39:44.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-26T06:33:06.000Z" objectID="{3A067CBC-DC0D-05D8-02F3-B2A77833F748}{23}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="101.66748046875" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-26T06:33:06.000Z" lastModifiedTime="2026-09-26T06:33:06.000Z" objectID="{3A067CBC-DC0D-05D8-02F3-B2A77833F748}{24}{B0}" alignment="left"><one:T><![CDATA[Original]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/page-lifecycle/offline-creation/cold/read/page-002.xml+1-1| ... | @@ -1,2 +1,2 @@ | ... | @@ -1,2 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{52FFB786-C5E9-07F5-1BE1-C5095C3F3F9B}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-09T05:06:26.000Z" lastModifiedTime="2026-09-09T05:08:15.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Offline author" lastModifiedBy="Offline author" creationTime="2026-09-09T05:06:26.000Z" lastModifiedTime="2026-09-09T05:06:26.000Z" objectID="{52FFB786-C5E9-07F5-1BE1-C5095C3F3F9B}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-09T05:06:26.000Z" objectID="{6FF29CE3-FA4E-08F5-0E60-DF83A72CF205}{1}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="97.35448455810547" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-09T05:06:26.000Z" lastModifiedTime="2026-09-09T05:06:26.000Z" objectID="{6FF29CE3-FA4E-08F5-0E60-DF83A72CF205}{3}{B0}" alignment="left"><one:T><![CDATA[Body 7]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{363E253C-E20B-038F-3B6E-740FF08C2966}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-26T06:33:07.000Z" lastModifiedTime="2026-09-26T06:39:45.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Offline author" lastModifiedBy="Offline author" creationTime="2026-09-26T06:33:07.000Z" lastModifiedTime="2026-09-26T06:33:07.000Z" objectID="{363E253C-E20B-038F-3B6E-740FF08C2966}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="Offline author" lastModifiedBy="Offline author" lastModifiedTime="2026-09-26T06:33:07.000Z" objectID="{FB857531-64AC-0D81-2D4A-97656A27EA6B}{8}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="97.35448455810547" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-26T06:33:07.000Z" lastModifiedTime="2026-09-26T06:33:07.000Z" objectID="{FB857531-64AC-0D81-2D4A-97656A27EA6B}{9}{B0}" alignment="left"><one:T><![CDATA[Body 2]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/page-lifecycle/offline-creation/cold/read/page-003.xml+1-1| ... | @@ -1,2 +1,2 @@ | ... | @@ -1,2 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{54F2D7D9-CAFE-0C6B-232B-2AE669F85BEA}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-09T05:06:25.000Z" lastModifiedTime="2026-09-09T05:08:15.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Offline author" lastModifiedBy="Offline author" creationTime="2026-09-09T05:06:25.000Z" lastModifiedTime="2026-09-09T05:06:25.000Z" objectID="{54F2D7D9-CAFE-0C6B-232B-2AE669F85BEA}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-09T05:06:25.000Z" objectID="{90835CA0-7D2C-0889-17BA-5D11B3E7B486}{1}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="97.35448455810547" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-09T05:06:25.000Z" lastModifiedTime="2026-09-09T05:06:25.000Z" objectID="{90835CA0-7D2C-0889-17BA-5D11B3E7B486}{3}{B0}" alignment="left"><one:T><![CDATA[Body 5]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{3720123D-2FCD-0950-2E1C-389C62F8A52F}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-26T06:33:08.000Z" lastModifiedTime="2026-09-26T06:39:45.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Offline author" lastModifiedBy="Offline author" creationTime="2026-09-26T06:33:08.000Z" lastModifiedTime="2026-09-26T06:33:08.000Z" objectID="{3720123D-2FCD-0950-2E1C-389C62F8A52F}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="Offline author" lastModifiedBy="Offline author" lastModifiedTime="2026-09-26T06:33:08.000Z" objectID="{FB857531-64AC-0D81-2D4A-97656A27EA6B}{35}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="102.9296875" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-26T06:33:08.000Z" lastModifiedTime="2026-09-26T06:33:08.000Z" objectID="{FB857531-64AC-0D81-2D4A-97656A27EA6B}{36}{B0}" alignment="left"><one:T><![CDATA[Body 11]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/page-lifecycle/offline-creation/cold/read/page-004.xml+1-1| ... | @@ -1,2 +1,2 @@ | ... | @@ -1,2 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{557FC918-74CB-09B0-19E7-3D1809D43E13}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-09T05:06:25.000Z" lastModifiedTime="2026-09-09T05:08:15.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Offline author" lastModifiedBy="Offline author" creationTime="2026-09-09T05:06:25.000Z" lastModifiedTime="2026-09-09T05:06:25.000Z" objectID="{557FC918-74CB-09B0-19E7-3D1809D43E13}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-09T05:06:25.000Z" objectID="{51D93968-7A84-0AD0-3C31-985726CCBCC2}{1}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="97.35448455810547" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-09T05:06:25.000Z" lastModifiedTime="2026-09-09T05:06:25.000Z" objectID="{51D93968-7A84-0AD0-3C31-985726CCBCC2}{3}{B0}" alignment="left"><one:T><![CDATA[Body 4]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{535A6811-A498-0BA3-2BE5-52623B656182}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-26T06:33:08.000Z" lastModifiedTime="2026-09-26T06:39:45.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Offline author" lastModifiedBy="Offline author" creationTime="2026-09-26T06:33:08.000Z" lastModifiedTime="2026-09-26T06:33:08.000Z" objectID="{535A6811-A498-0BA3-2BE5-52623B656182}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="Offline author" lastModifiedBy="Offline author" lastModifiedTime="2026-09-26T06:33:08.000Z" objectID="{FB857531-64AC-0D81-2D4A-97656A27EA6B}{29}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="97.35448455810547" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-26T06:33:08.000Z" lastModifiedTime="2026-09-26T06:33:08.000Z" objectID="{FB857531-64AC-0D81-2D4A-97656A27EA6B}{30}{B0}" alignment="left"><one:T><![CDATA[Body 9]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/page-lifecycle/offline-creation/cold/read/page-005.xml+1-1| ... | @@ -1,2 +1,2 @@ | ... | @@ -1,2 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{6CB51A17-61D4-0DBF-2C43-4CB4C9D2B6A1}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-09T05:06:26.000Z" lastModifiedTime="2026-09-09T05:08:15.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Offline author" lastModifiedBy="Offline author" creationTime="2026-09-09T05:06:26.000Z" lastModifiedTime="2026-09-09T05:06:26.000Z" objectID="{6CB51A17-61D4-0DBF-2C43-4CB4C9D2B6A1}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-09T05:06:26.000Z" objectID="{7725B82C-645F-0B2B-37FE-E1431BEF768F}{1}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="97.35448455810547" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-09T05:06:26.000Z" lastModifiedTime="2026-09-09T05:06:26.000Z" objectID="{7725B82C-645F-0B2B-37FE-E1431BEF768F}{3}{B0}" alignment="left"><one:T><![CDATA[Body 9]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{5BCF0FAC-7A3F-0144-15E4-A5B7CD35716B}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-26T06:33:07.000Z" lastModifiedTime="2026-09-26T06:39:45.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Offline author" lastModifiedBy="Offline author" creationTime="2026-09-26T06:33:07.000Z" lastModifiedTime="2026-09-26T06:33:07.000Z" objectID="{5BCF0FAC-7A3F-0144-15E4-A5B7CD35716B}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="Offline author" lastModifiedBy="Offline author" lastModifiedTime="2026-09-26T06:33:07.000Z" objectID="{FB857531-64AC-0D81-2D4A-97656A27EA6B}{20}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="97.35448455810547" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-26T06:33:07.000Z" lastModifiedTime="2026-09-26T06:33:07.000Z" objectID="{FB857531-64AC-0D81-2D4A-97656A27EA6B}{21}{B0}" alignment="left"><one:T><![CDATA[Body 6]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/page-lifecycle/offline-creation/cold/read/page-006.xml+1-1| ... | @@ -1,2 +1,2 @@ | ... | @@ -1,2 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{7CA0430A-8905-04E4-0455-CFE53ECEE2AA}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-09T05:06:25.000Z" lastModifiedTime="2026-09-09T05:08:15.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Offline author" lastModifiedBy="Offline author" creationTime="2026-09-09T05:06:25.000Z" lastModifiedTime="2026-09-09T05:06:25.000Z" objectID="{7CA0430A-8905-04E4-0455-CFE53ECEE2AA}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-09T05:06:25.000Z" objectID="{A1B77445-30F3-071B-2616-CBF8748C32AB}{1}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="97.35448455810547" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-09T05:06:25.000Z" lastModifiedTime="2026-09-09T05:06:25.000Z" objectID="{A1B77445-30F3-071B-2616-CBF8748C32AB}{3}{B0}" alignment="left"><one:T><![CDATA[Body 3]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{670CA118-DAE0-03FD-32C3-C0BBC132147E}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-26T06:33:08.000Z" lastModifiedTime="2026-09-26T06:39:45.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Offline author" lastModifiedBy="Offline author" creationTime="2026-09-26T06:33:08.000Z" lastModifiedTime="2026-09-26T06:33:08.000Z" objectID="{670CA118-DAE0-03FD-32C3-C0BBC132147E}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="Offline author" lastModifiedBy="Offline author" lastModifiedTime="2026-09-26T06:33:08.000Z" objectID="{FB857531-64AC-0D81-2D4A-97656A27EA6B}{26}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="97.35448455810547" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-26T06:33:08.000Z" lastModifiedTime="2026-09-26T06:33:08.000Z" objectID="{FB857531-64AC-0D81-2D4A-97656A27EA6B}{27}{B0}" alignment="left"><one:T><![CDATA[Body 8]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/page-lifecycle/offline-creation/cold/read/page-007.xml+1-1| ... | @@ -1,2 +1,2 @@ | ... | @@ -1,2 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{87E24DEE-963B-09DA-1E68-D198DC52EDA5}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-09T05:06:26.000Z" lastModifiedTime="2026-09-09T05:08:15.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Offline author" lastModifiedBy="Offline author" creationTime="2026-09-09T05:06:26.000Z" lastModifiedTime="2026-09-09T05:06:26.000Z" objectID="{87E24DEE-963B-09DA-1E68-D198DC52EDA5}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-09T05:06:27.000Z" objectID="{C3B0AAD1-B11A-0DCA-287E-D00A5BDC7028}{1}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="102.9296875" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-09T05:06:26.000Z" lastModifiedTime="2026-09-09T05:06:27.000Z" objectID="{C3B0AAD1-B11A-0DCA-287E-D00A5BDC7028}{3}{B0}" alignment="left"><one:T><![CDATA[Body 10]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{6BD09F5F-AC47-06D8-3796-A163670C2EFD}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-26T06:33:07.000Z" lastModifiedTime="2026-09-26T06:39:45.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Offline author" lastModifiedBy="Offline author" creationTime="2026-09-26T06:33:07.000Z" lastModifiedTime="2026-09-26T06:33:07.000Z" objectID="{6BD09F5F-AC47-06D8-3796-A163670C2EFD}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="Offline author" lastModifiedBy="Offline author" lastModifiedTime="2026-09-26T06:33:07.000Z" objectID="{FB857531-64AC-0D81-2D4A-97656A27EA6B}{14}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="97.35448455810547" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-26T06:33:07.000Z" lastModifiedTime="2026-09-26T06:33:07.000Z" objectID="{FB857531-64AC-0D81-2D4A-97656A27EA6B}{15}{B0}" alignment="left"><one:T><![CDATA[Body 4]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/page-lifecycle/offline-creation/cold/read/page-008.xml+1-1| ... | @@ -1,2 +1,2 @@ | ... | @@ -1,2 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{9EE67EFB-DA69-05C0-334D-18E6DCBBE33B}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-09T05:06:25.000Z" lastModifiedTime="2026-09-09T05:08:15.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Offline author" lastModifiedBy="Offline author" creationTime="2026-09-09T05:06:25.000Z" lastModifiedTime="2026-09-09T05:06:25.000Z" objectID="{9EE67EFB-DA69-05C0-334D-18E6DCBBE33B}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-09T05:06:26.000Z" objectID="{DF4AA824-AF22-0491-1F7A-0EEF3AF04369}{1}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="97.35448455810547" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-09T05:06:25.000Z" lastModifiedTime="2026-09-09T05:06:26.000Z" objectID="{DF4AA824-AF22-0491-1F7A-0EEF3AF04369}{3}{B0}" alignment="left"><one:T><![CDATA[Body 6]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{849FA331-A262-0BFD-1424-6ABB50BCD8C7}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-26T06:33:06.000Z" lastModifiedTime="2026-09-26T06:39:44.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Offline author" lastModifiedBy="Offline author" creationTime="2026-09-26T06:33:06.000Z" lastModifiedTime="2026-09-26T06:33:06.000Z" objectID="{849FA331-A262-0BFD-1424-6ABB50BCD8C7}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="Offline author" lastModifiedBy="Offline author" lastModifiedTime="2026-09-26T06:33:06.000Z" objectID="{FB857531-64AC-0D81-2D4A-97656A27EA6B}{2}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="97.35448455810547" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-26T06:33:06.000Z" lastModifiedTime="2026-09-26T06:33:06.000Z" objectID="{FB857531-64AC-0D81-2D4A-97656A27EA6B}{3}{B0}" alignment="left"><one:T><![CDATA[Body 0]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/page-lifecycle/offline-creation/cold/read/page-009.xml+1-1| ... | @@ -1,2 +1,2 @@ | ... | @@ -1,2 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{AC1E7B39-43AC-0370-2960-486A10D01E43}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-09T05:06:27.000Z" lastModifiedTime="2026-09-09T05:08:15.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Offline author" lastModifiedBy="Offline author" creationTime="2026-09-09T05:06:27.000Z" lastModifiedTime="2026-09-09T05:06:27.000Z" objectID="{AC1E7B39-43AC-0370-2960-486A10D01E43}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-09T05:06:27.000Z" objectID="{423E0C00-FA84-099F-113B-397CAEE28758}{1}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="102.9296875" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-09T05:06:27.000Z" lastModifiedTime="2026-09-09T05:06:27.000Z" objectID="{423E0C00-FA84-099F-113B-397CAEE28758}{3}{B0}" alignment="left"><one:T><![CDATA[Body 11]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{8EE0AC66-288F-02EF-1E6D-FC56C315C68F}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-26T06:33:07.000Z" lastModifiedTime="2026-09-26T06:39:45.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Offline author" lastModifiedBy="Offline author" creationTime="2026-09-26T06:33:07.000Z" lastModifiedTime="2026-09-26T06:33:07.000Z" objectID="{8EE0AC66-288F-02EF-1E6D-FC56C315C68F}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="Offline author" lastModifiedBy="Offline author" lastModifiedTime="2026-09-26T06:33:08.000Z" objectID="{FB857531-64AC-0D81-2D4A-97656A27EA6B}{23}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="97.35448455810547" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-26T06:33:08.000Z" lastModifiedTime="2026-09-26T06:33:08.000Z" objectID="{FB857531-64AC-0D81-2D4A-97656A27EA6B}{24}{B0}" alignment="left"><one:T><![CDATA[Body 7]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/page-lifecycle/offline-creation/cold/read/page-010.xml+1-1| ... | @@ -1,2 +1,2 @@ | ... | @@ -1,2 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{AC84259B-D9B6-0A59-2748-93C35602B0BD}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-09T05:06:24.000Z" lastModifiedTime="2026-09-09T05:08:15.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Offline author" lastModifiedBy="Offline author" creationTime="2026-09-09T05:06:24.000Z" lastModifiedTime="2026-09-09T05:06:24.000Z" objectID="{AC84259B-D9B6-0A59-2748-93C35602B0BD}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-09T05:06:24.000Z" objectID="{2A37A85A-3C9C-0CBE-2D9A-253D334E6301}{1}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="97.35448455810547" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-09T05:06:24.000Z" lastModifiedTime="2026-09-09T05:06:24.000Z" objectID="{2A37A85A-3C9C-0CBE-2D9A-253D334E6301}{3}{B0}" alignment="left"><one:T><![CDATA[Body 1]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{91186C32-65B4-0578-030C-10DA1738633A}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-26T06:33:06.000Z" lastModifiedTime="2026-09-26T06:39:45.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Offline author" lastModifiedBy="Offline author" creationTime="2026-09-26T06:33:06.000Z" lastModifiedTime="2026-09-26T06:33:06.000Z" objectID="{91186C32-65B4-0578-030C-10DA1738633A}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="Offline author" lastModifiedBy="Offline author" lastModifiedTime="2026-09-26T06:33:07.000Z" objectID="{FB857531-64AC-0D81-2D4A-97656A27EA6B}{5}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="97.35448455810547" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-26T06:33:07.000Z" lastModifiedTime="2026-09-26T06:33:07.000Z" objectID="{FB857531-64AC-0D81-2D4A-97656A27EA6B}{6}{B0}" alignment="left"><one:T><![CDATA[Body 1]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/page-lifecycle/offline-creation/cold/read/page-011.xml+1-1| ... | @@ -1,2 +1,2 @@ | ... | @@ -1,2 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{CC3DEC87-5707-084C-0528-649AB24AD8B7}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-09T05:06:24.000Z" lastModifiedTime="2026-09-09T05:08:15.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Offline author" lastModifiedBy="Offline author" creationTime="2026-09-09T05:06:24.000Z" lastModifiedTime="2026-09-09T05:06:24.000Z" objectID="{CC3DEC87-5707-084C-0528-649AB24AD8B7}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-09T05:06:25.000Z" objectID="{DD7A7A6D-CF78-0C0A-3512-66C69436AC88}{1}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="97.35448455810547" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-09T05:06:24.000Z" lastModifiedTime="2026-09-09T05:06:25.000Z" objectID="{DD7A7A6D-CF78-0C0A-3512-66C69436AC88}{3}{B0}" alignment="left"><one:T><![CDATA[Body 2]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{CC15B979-9239-00D3-0F06-26192E32E6AE}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-26T06:33:08.000Z" lastModifiedTime="2026-09-26T06:39:45.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Offline author" lastModifiedBy="Offline author" creationTime="2026-09-26T06:33:08.000Z" lastModifiedTime="2026-09-26T06:33:08.000Z" objectID="{CC15B979-9239-00D3-0F06-26192E32E6AE}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="Offline author" lastModifiedBy="Offline author" lastModifiedTime="2026-09-26T06:33:08.000Z" objectID="{FB857531-64AC-0D81-2D4A-97656A27EA6B}{32}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="102.9296875" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-26T06:33:08.000Z" lastModifiedTime="2026-09-26T06:33:08.000Z" objectID="{FB857531-64AC-0D81-2D4A-97656A27EA6B}{33}{B0}" alignment="left"><one:T><![CDATA[Body 10]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/page-lifecycle/offline-creation/cold/read/page-012.xml+1-1| ... | @@ -1,2 +1,2 @@ | ... | @@ -1,2 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{D3E5FB89-79C3-05BA-1805-E25CE31ED175}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-09T05:06:24.000Z" lastModifiedTime="2026-09-09T05:08:15.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Offline author" lastModifiedBy="Offline author" creationTime="2026-09-09T05:06:24.000Z" lastModifiedTime="2026-09-09T05:06:24.000Z" objectID="{D3E5FB89-79C3-05BA-1805-E25CE31ED175}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-09T05:06:24.000Z" objectID="{72A18D56-4556-035E-3AD5-CF9B88EA399E}{1}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="97.35448455810547" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-09T05:06:24.000Z" lastModifiedTime="2026-09-09T05:06:24.000Z" objectID="{72A18D56-4556-035E-3AD5-CF9B88EA399E}{3}{B0}" alignment="left"><one:T><![CDATA[Body 0]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{DAF8F05F-4D73-0BFF-1A5C-60D6A0F48D49}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-26T06:33:07.000Z" lastModifiedTime="2026-09-26T06:39:45.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Offline author" lastModifiedBy="Offline author" creationTime="2026-09-26T06:33:07.000Z" lastModifiedTime="2026-09-26T06:33:07.000Z" objectID="{DAF8F05F-4D73-0BFF-1A5C-60D6A0F48D49}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="Offline author" lastModifiedBy="Offline author" lastModifiedTime="2026-09-26T06:33:07.000Z" objectID="{FB857531-64AC-0D81-2D4A-97656A27EA6B}{11}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="97.35448455810547" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-26T06:33:07.000Z" lastModifiedTime="2026-09-26T06:33:07.000Z" objectID="{FB857531-64AC-0D81-2D4A-97656A27EA6B}{12}{B0}" alignment="left"><one:T><![CDATA[Body 3]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/page-lifecycle/offline-creation/provenance.json+3-3| ... | @@ -1,6 +1,6 @@ | ... | @@ -1,6 +1,6 @@ |
| 1 | { | 1 | { |
| 2 | "run": { | 2 | "run": { |
| 3 | "notebook": "/Users/clo/dev/one/evidence/m10/offline-page-candidate", | 3 | "notebook": "/Users/clo/dev/one/evidence/titles/offline-creation", |
| 4 | "expected_pages": 13, | 4 | "expected_pages": 13, |
| 5 | "author": null, | 5 | "author": null, |
| 6 | "author_timeout_seconds": 600, | 6 | "author_timeout_seconds": 600, |
| ... | @@ -18,8 +18,8 @@ | ... | @@ -18,8 +18,8 @@ |
| 18 | } | 18 | } |
| 19 | }, | 19 | }, |
| 20 | "machine": { | 20 | "machine": { |
| 21 | "name": "m6-cc15a5ee", | 21 | "name": "m6-4d84f1a7", |
| 22 | "hostname": "ONE-M6-CC15A5EE" | 22 | "hostname": "ONE-M6-4D84F1A7" |
| 23 | }, | 23 | }, |
| 24 | "teardown": { | 24 | "teardown": { |
| 25 | "absent": true | 25 | "absent": true |
corpus/page-lifecycle/offline-edits/provenance.json+12-6| ... | @@ -633,7 +633,7 @@ | ... | @@ -633,7 +633,7 @@ |
| 633 | }, | 633 | }, |
| 634 | "twelve-clients": { | 634 | "twelve-clients": { |
| 635 | "run": { | 635 | "run": { |
| 636 | "notebook": "/private/tmp/m33/page-clients2/candidate", | 636 | "notebook": "/Users/clo/dev/one/evidence/titles/twelve-clients", |
| 637 | "expected_pages": 37, | 637 | "expected_pages": 37, |
| 638 | "author": null, | 638 | "author": null, |
| 639 | "author_timeout_seconds": 600, | 639 | "author_timeout_seconds": 600, |
| ... | @@ -651,18 +651,24 @@ | ... | @@ -651,18 +651,24 @@ |
| 651 | } | 651 | } |
| 652 | }, | 652 | }, |
| 653 | "machine": { | 653 | "machine": { |
| 654 | "name": "m6-16c2e4d4", | 654 | "name": "m6-8eabb42e", |
| 655 | "hostname": "ONE-M6-16C2E4D4" | 655 | "hostname": "ONE-M6-8EABB42E" |
| 656 | }, | 656 | }, |
| 657 | "teardown": { | 657 | "teardown": { |
| 658 | "absent": true | 658 | "absent": true |
| 659 | }, | 659 | }, |
| 660 | "source": [ | 660 | "source": [ |
| 661 | { | ||
| 662 | "path": "manifest.json", | ||
| 663 | "bytes": 292907, | ||
| 664 | "sha256": "fea1d64b22da84d023d4c9014175f8e92a13acba099c7a1a3f8eae30d6c6407f", | ||
| 665 | "mtime_ns": 1790404395466294564 | ||
| 666 | }, | ||
| 661 | { | 667 | { |
| 662 | "path": "pages.one", | 668 | "path": "pages.one", |
| 663 | "bytes": 155344, | 669 | "bytes": 152360, |
| 664 | "sha256": "623ade8b5ee6fc837ae63d5293791039ecd17da9cb8bc5e243ea20cadf37958c", | 670 | "sha256": "302b6d2d31bc8f539a2217aed3391973678576b0aea1ce82c6e21760d6402e14", |
| 665 | "mtime_ns": 1789124063040528117 | 671 | "mtime_ns": 1790404395457867619 |
| 666 | } | 672 | } |
| 667 | ] | 673 | ] |
| 668 | }, | 674 | }, |
corpus/page-lifecycle/offline-edits/twelve-clients/candidate/manifest.json+1996-1204| ... | @@ -1,151 +1,151 @@ | ... | @@ -1,151 +1,151 @@ |
| 1 | { | 1 | { |
| 2 | "expected_order": [ | 2 | "expected_order": [ |
| 3 | [ | 3 | [ |
| 4 | "{869D5929-6E5E-4EDB-9639-4C56977DD447},1", | 4 | "{0B37E922-551A-48F8-BCAC-4AB3503B4D28},1", |
| 5 | 1 | 5 | 1 |
| 6 | ], | 6 | ], |
| 7 | [ | 7 | [ |
| 8 | "{0E98EE82-B78F-4872-8BD9-5D9B23A817C5},1", | 8 | "{DAFF533D-608B-4735-BA1D-2354E8CB8A69},1", |
| 9 | 1 | 9 | 1 |
| 10 | ], | 10 | ], |
| 11 | [ | 11 | [ |
| 12 | "{B0B53F94-0105-4C59-B65B-9473F49BFF41},1", | 12 | "{74492C4A-164B-40E9-B297-4D3B4685347D},1", |
| 13 | 1 | 13 | 1 |
| 14 | ], | 14 | ], |
| 15 | [ | 15 | [ |
| 16 | "{C837A033-F055-4455-B45F-17D82016921C},1", | 16 | "{8B766F11-2A4F-4D95-8473-D3CA68129E11},1", |
| 17 | 2 | 17 | 2 |
| 18 | ], | 18 | ], |
| 19 | [ | 19 | [ |
| 20 | "{57E835EA-2799-4118-B55C-7BB1E51C6941},1", | 20 | "{31FFA5F9-C1B8-412D-A2D6-7ACC3CC16FD0},1", |
| 21 | 1 | 21 | 1 |
| 22 | ], | 22 | ], |
| 23 | [ | 23 | [ |
| 24 | "{89D86121-BF5C-418B-BB92-CBFFA8EF545F},1", | 24 | "{28E0CC5F-68BC-4A86-9ED7-114FFD3BAE9A},1", |
| 25 | 1 | 25 | 1 |
| 26 | ], | 26 | ], |
| 27 | [ | 27 | [ |
| 28 | "{F7EECCCF-B6B4-44F2-A543-36837EC18B8A},1", | 28 | "{AFCD09F6-1085-4582-BDB2-4B91E15D2088},1", |
| 29 | 2 | 29 | 2 |
| 30 | ], | 30 | ], |
| 31 | [ | 31 | [ |
| 32 | "{EB3940F1-F255-42BE-8C31-DD09E86DBAAF},1", | 32 | "{5AB37A0F-5702-4C8E-A8C6-79ED6ED1E69F},1", |
| 33 | 1 | 33 | 1 |
| 34 | ], | 34 | ], |
| 35 | [ | 35 | [ |
| 36 | "{361A0F09-EA63-4F2D-9620-C061D4E4707E},1", | 36 | "{A94D5882-C902-4CAE-B38E-2ED56EEF177F},1", |
| 37 | 1 | 37 | 1 |
| 38 | ], | 38 | ], |
| 39 | [ | 39 | [ |
| 40 | "{C2870D07-8ADC-44BB-9F12-15D6C3708689},1", | 40 | "{F212795E-0E4B-4844-A8A6-ABD7F7025124},1", |
| 41 | 2 | 41 | 2 |
| 42 | ], | 42 | ], |
| 43 | [ | 43 | [ |
| 44 | "{ED9769DD-99D6-4BE2-B980-580DA9C4DFCE},1", | 44 | "{3E966EF8-B7B4-483F-B241-8F21DF318AB3},1", |
| 45 | 1 | 45 | 1 |
| 46 | ], | 46 | ], |
| 47 | [ | 47 | [ |
| 48 | "{1E0C6331-32C9-4EF9-8087-150A8669FA91},1", | 48 | "{1786F2CA-0A43-4081-8B55-B207EEE0331C},1", |
| 49 | 1 | 49 | 1 |
| 50 | ], | 50 | ], |
| 51 | [ | 51 | [ |
| 52 | "{17C30054-7129-4A10-9D00-4D5545E4F4BA},1", | 52 | "{0D361C76-92FD-4B76-901B-809C2C3C3CE6},1", |
| 53 | 2 | 53 | 2 |
| 54 | ], | 54 | ], |
| 55 | [ | 55 | [ |
| 56 | "{BD0EF658-4D0F-42A7-B546-B4F4A77D246B},1", | 56 | "{551D2637-2AA7-4923-B2DD-31C2E1489FD2},1", |
| 57 | 1 | 57 | 1 |
| 58 | ], | 58 | ], |
| 59 | [ | 59 | [ |
| 60 | "{4C5FBA5B-E348-4750-ACE4-E9694D527BFF},1", | 60 | "{AE89305A-73F4-46B5-B41D-18884685D896},1", |
| 61 | 1 | 61 | 1 |
| 62 | ], | 62 | ], |
| 63 | [ | 63 | [ |
| 64 | "{6FEA70FA-1451-4A27-8A8E-C4CC3B713CF6},1", | 64 | "{CBF82C86-3A8B-457F-92A4-1AC7BF436CE9},1", |
| 65 | 2 | 65 | 2 |
| 66 | ], | 66 | ], |
| 67 | [ | 67 | [ |
| 68 | "{7E9041BD-301B-4B5B-9456-1C92050E4914},1", | 68 | "{0F6ABB5E-A021-4E1D-B187-7A72E74000DC},1", |
| 69 | 1 | 69 | 1 |
| 70 | ], | 70 | ], |
| 71 | [ | 71 | [ |
| 72 | "{68A1E0E1-7A39-4AB6-AD71-4D8A18878D79},1", | 72 | "{42010094-E377-4A97-9395-AD45808D0552},1", |
| 73 | 1 | 73 | 1 |
| 74 | ], | 74 | ], |
| 75 | [ | 75 | [ |
| 76 | "{EFA77BAE-8A98-4176-B536-B615D1D2ED2B},1", | 76 | "{F9DA3139-5856-4B11-95C0-356683E3ACFE},1", |
| 77 | 2 | 77 | 2 |
| 78 | ], | 78 | ], |
| 79 | [ | 79 | [ |
| 80 | "{C50BF7FC-F848-4B91-B8C8-743D0AA5F195},1", | 80 | "{B03B973C-50BC-4B9A-9914-B1D5C0CF6197},1", |
| 81 | 1 | 81 | 1 |
| 82 | ], | 82 | ], |
| 83 | [ | 83 | [ |
| 84 | "{87AC0239-51A5-47C6-BAF4-926CCF56385C},1", | 84 | "{439B80E0-9D4C-4A2F-9163-4E301491364D},1", |
| 85 | 1 | 85 | 1 |
| 86 | ], | 86 | ], |
| 87 | [ | 87 | [ |
| 88 | "{FD8D07D0-C447-4516-A7DB-F8522B77D4A7},1", | 88 | "{A459C474-E6EB-4BE5-9193-271799B2860B},1", |
| 89 | 2 | 89 | 2 |
| 90 | ], | 90 | ], |
| 91 | [ | 91 | [ |
| 92 | "{7F6702B3-C977-436F-82AF-403E2BE950E7},1", | 92 | "{C4FF8FE4-4C43-461F-98DD-EB1FF2A863B2},1", |
| 93 | 1 | 93 | 1 |
| 94 | ], | 94 | ], |
| 95 | [ | 95 | [ |
| 96 | "{D2E1FE57-F45D-49C3-B388-95222ACA3886},1", | 96 | "{4ED14CEE-8560-4125-B488-9945C116741E},1", |
| 97 | 1 | 97 | 1 |
| 98 | ], | 98 | ], |
| 99 | [ | 99 | [ |
| 100 | "{688C4EA1-80F5-4D85-B9BB-F8633FF3C0CA},1", | 100 | "{932BE37F-05AA-4EFB-85D8-A33445E738B3},1", |
| 101 | 2 | 101 | 2 |
| 102 | ], | 102 | ], |
| 103 | [ | 103 | [ |
| 104 | "{D2197C55-D962-4DF0-8B14-26F88C06D6FA},1", | 104 | "{D045EAAF-ACE9-4B4F-979B-E348A4ADE693},1", |
| 105 | 1 | 105 | 1 |
| 106 | ], | 106 | ], |
| 107 | [ | 107 | [ |
| 108 | "{754FA957-C432-4502-A5CF-EE44449462C0},1", | 108 | "{9B755368-1D49-4D7E-8D33-6DC536F57848},1", |
| 109 | 1 | 109 | 1 |
| 110 | ], | 110 | ], |
| 111 | [ | 111 | [ |
| 112 | "{87B83BA8-A906-4F71-A6CA-3E8133A75D34},1", | 112 | "{5CA4C59F-B400-4BC6-8B90-30ECAC35CB43},1", |
| 113 | 2 | 113 | 2 |
| 114 | ], | 114 | ], |
| 115 | [ | 115 | [ |
| 116 | "{406687D7-F9E9-491B-85DA-166DE6CD5A60},1", | 116 | "{C6E54364-6CD0-4764-8180-873030B9EA53},1", |
| 117 | 1 | 117 | 1 |
| 118 | ], | 118 | ], |
| 119 | [ | 119 | [ |
| 120 | "{F2D6AEBB-98EA-4F10-991F-5243E74AF450},1", | 120 | "{D63FB383-FB37-44F9-AEBB-E868E903A2BB},1", |
| 121 | 1 | 121 | 1 |
| 122 | ], | 122 | ], |
| 123 | [ | 123 | [ |
| 124 | "{1F73BF36-9FC2-495D-844D-B370D5C47DE7},1", | 124 | "{DEEC391B-F2D1-4EB1-BEB1-57598DFBB982},1", |
| 125 | 2 | 125 | 2 |
| 126 | ], | 126 | ], |
| 127 | [ | 127 | [ |
| 128 | "{189EA744-BE13-467A-95BA-2667A134C0B0},1", | 128 | "{89F1748D-6749-4AD0-BAF6-07E9F9C058E5},1", |
| 129 | 1 | 129 | 1 |
| 130 | ], | 130 | ], |
| 131 | [ | 131 | [ |
| 132 | "{E9BD343F-4A66-4DE2-81F9-3789CCA22818},1", | 132 | "{9E7616ED-DE19-4625-8EA0-0C5DEB18B690},1", |
| 133 | 1 | 133 | 1 |
| 134 | ], | 134 | ], |
| 135 | [ | 135 | [ |
| 136 | "{017F5881-F818-4C7C-B9F9-6BCB4085F1D0},1", | 136 | "{00B10C58-6649-428D-A081-3EBBA6E88E97},1", |
| 137 | 2 | 137 | 2 |
| 138 | ], | 138 | ], |
| 139 | [ | 139 | [ |
| 140 | "{CCBB5969-BCE8-498B-BE1A-98FD3A44DB1A},1", | 140 | "{F721C9D5-9A6A-405F-94C9-9B85A7EEB980},1", |
| 141 | 1 | 141 | 1 |
| 142 | ], | 142 | ], |
| 143 | [ | 143 | [ |
| 144 | "{CD0D17F4-25D8-4076-9AF7-47D9CDEFCF0A},1", | 144 | "{58E99482-710A-4938-8CFB-88A1C9B946C9},1", |
| 145 | 1 | 145 | 1 |
| 146 | ], | 146 | ], |
| 147 | [ | 147 | [ |
| 148 | "{2B736B19-2D9C-4E76-A732-478D533D717A},1", | 148 | "{15A45C01-A9E8-42E2-A267-C7FCE728815E},1", |
| 149 | 2 | 149 | 2 |
| 150 | ] | 150 | ] |
| 151 | ], | 151 | ], |
| ... | @@ -158,215 +158,215 @@ | ... | @@ -158,215 +158,215 @@ |
| 158 | "edits": [ | 158 | "edits": [ |
| 159 | { | 159 | { |
| 160 | "guid": [ | 160 | "guid": [ |
| 161 | 86, | 161 | 155, |
| 162 | 0, | 162 | 49, |
| 163 | 111, | 163 | 40, |
| 164 | 192, | 164 | 55, |
| 165 | 110, | ||
| 166 | 61, | ||
| 167 | 248, | ||
| 168 | 78, | ||
| 169 | 186, | ||
| 170 | 245, | ||
| 171 | 33, | ||
| 172 | 60, | ||
| 173 | 24, | ||
| 165 | 138, | 174 | 138, |
| 166 | 101, | 175 | 51, |
| 167 | 192, | 176 | 39 |
| 168 | 76, | ||
| 169 | 185, | ||
| 170 | 152, | ||
| 171 | 225, | ||
| 172 | 226, | ||
| 173 | 79, | ||
| 174 | 192, | ||
| 175 | 46, | ||
| 176 | 40 | ||
| 177 | ], | 177 | ], |
| 178 | "level": 1, | 178 | "level": 1, |
| 179 | "position": { | 179 | "position": { |
| 180 | "Before": "{B0B53F94-0105-4C59-B65B-9473F49BFF41},1" | 180 | "Before": "{74492C4A-164B-40E9-B297-4D3B4685347D},1" |
| 181 | }, | 181 | }, |
| 182 | "space": "{0E98EE82-B78F-4872-8BD9-5D9B23A817C5},1" | 182 | "space": "{DAFF533D-608B-4735-BA1D-2354E8CB8A69},1" |
| 183 | }, | 183 | }, |
| 184 | { | 184 | { |
| 185 | "guid": [ | 185 | "guid": [ |
| 186 | 133, | 186 | 124, |
| 187 | 52, | 187 | 69, |
| 188 | 164, | ||
| 189 | 134, | ||
| 190 | 185, | ||
| 191 | 207, | ||
| 192 | 146, | ||
| 188 | 67, | 193 | 67, |
| 189 | 104, | 194 | 144, |
| 190 | 247, | 195 | 255, |
| 191 | 13, | 196 | 194, |
| 192 | 120, | 197 | 10, |
| 193 | 79, | 198 | 29, |
| 194 | 190, | 199 | 94, |
| 195 | 78, | 200 | 178, |
| 196 | 126, | 201 | 200 |
| 197 | 126, | ||
| 198 | 12, | ||
| 199 | 116, | ||
| 200 | 230, | ||
| 201 | 238 | ||
| 202 | ], | 202 | ], |
| 203 | "level": 2, | 203 | "level": 2, |
| 204 | "position": "Keep", | 204 | "position": "Keep", |
| 205 | "space": "{C837A033-F055-4455-B45F-17D82016921C},1" | 205 | "space": "{8B766F11-2A4F-4D95-8473-D3CA68129E11},1" |
| 206 | } | 206 | } |
| 207 | ], | 207 | ], |
| 208 | "observed": [ | 208 | "observed": [ |
| 209 | [ | 209 | [ |
| 210 | "{869D5929-6E5E-4EDB-9639-4C56977DD447},1", | 210 | "{0B37E922-551A-48F8-BCAC-4AB3503B4D28},1", |
| 211 | 1 | 211 | 1 |
| 212 | ], | 212 | ], |
| 213 | [ | 213 | [ |
| 214 | "{B0B53F94-0105-4C59-B65B-9473F49BFF41},1", | 214 | "{74492C4A-164B-40E9-B297-4D3B4685347D},1", |
| 215 | 1 | 215 | 1 |
| 216 | ], | 216 | ], |
| 217 | [ | 217 | [ |
| 218 | "{0E98EE82-B78F-4872-8BD9-5D9B23A817C5},1", | 218 | "{DAFF533D-608B-4735-BA1D-2354E8CB8A69},1", |
| 219 | 1 | 219 | 1 |
| 220 | ], | 220 | ], |
| 221 | [ | 221 | [ |
| 222 | "{C837A033-F055-4455-B45F-17D82016921C},1", | 222 | "{8B766F11-2A4F-4D95-8473-D3CA68129E11},1", |
| 223 | 1 | 223 | 1 |
| 224 | ], | 224 | ], |
| 225 | [ | 225 | [ |
| 226 | "{89D86121-BF5C-418B-BB92-CBFFA8EF545F},1", | 226 | "{28E0CC5F-68BC-4A86-9ED7-114FFD3BAE9A},1", |
| 227 | 1 | 227 | 1 |
| 228 | ], | 228 | ], |
| 229 | [ | 229 | [ |
| 230 | "{57E835EA-2799-4118-B55C-7BB1E51C6941},1", | 230 | "{31FFA5F9-C1B8-412D-A2D6-7ACC3CC16FD0},1", |
| 231 | 1 | 231 | 1 |
| 232 | ], | 232 | ], |
| 233 | [ | 233 | [ |
| 234 | "{F7EECCCF-B6B4-44F2-A543-36837EC18B8A},1", | 234 | "{AFCD09F6-1085-4582-BDB2-4B91E15D2088},1", |
| 235 | 1 | 235 | 1 |
| 236 | ], | 236 | ], |
| 237 | [ | 237 | [ |
| 238 | "{361A0F09-EA63-4F2D-9620-C061D4E4707E},1", | 238 | "{A94D5882-C902-4CAE-B38E-2ED56EEF177F},1", |
| 239 | 1 | 239 | 1 |
| 240 | ], | 240 | ], |
| 241 | [ | 241 | [ |
| 242 | "{EB3940F1-F255-42BE-8C31-DD09E86DBAAF},1", | 242 | "{5AB37A0F-5702-4C8E-A8C6-79ED6ED1E69F},1", |
| 243 | 1 | 243 | 1 |
| 244 | ], | 244 | ], |
| 245 | [ | 245 | [ |
| 246 | "{C2870D07-8ADC-44BB-9F12-15D6C3708689},1", | 246 | "{F212795E-0E4B-4844-A8A6-ABD7F7025124},1", |
| 247 | 1 | 247 | 1 |
| 248 | ], | 248 | ], |
| 249 | [ | 249 | [ |
| 250 | "{1E0C6331-32C9-4EF9-8087-150A8669FA91},1", | 250 | "{1786F2CA-0A43-4081-8B55-B207EEE0331C},1", |
| 251 | 1 | 251 | 1 |
| 252 | ], | 252 | ], |
| 253 | [ | 253 | [ |
| 254 | "{ED9769DD-99D6-4BE2-B980-580DA9C4DFCE},1", | 254 | "{3E966EF8-B7B4-483F-B241-8F21DF318AB3},1", |
| 255 | 1 | 255 | 1 |
| 256 | ], | 256 | ], |
| 257 | [ | 257 | [ |
| 258 | "{17C30054-7129-4A10-9D00-4D5545E4F4BA},1", | 258 | "{0D361C76-92FD-4B76-901B-809C2C3C3CE6},1", |
| 259 | 1 | 259 | 1 |
| 260 | ], | 260 | ], |
| 261 | [ | 261 | [ |
| 262 | "{4C5FBA5B-E348-4750-ACE4-E9694D527BFF},1", | 262 | "{AE89305A-73F4-46B5-B41D-18884685D896},1", |
| 263 | 1 | 263 | 1 |
| 264 | ], | 264 | ], |
| 265 | [ | 265 | [ |
| 266 | "{BD0EF658-4D0F-42A7-B546-B4F4A77D246B},1", | 266 | "{551D2637-2AA7-4923-B2DD-31C2E1489FD2},1", |
| 267 | 1 | 267 | 1 |
| 268 | ], | 268 | ], |
| 269 | [ | 269 | [ |
| 270 | "{6FEA70FA-1451-4A27-8A8E-C4CC3B713CF6},1", | 270 | "{CBF82C86-3A8B-457F-92A4-1AC7BF436CE9},1", |
| 271 | 1 | 271 | 1 |
| 272 | ], | 272 | ], |
| 273 | [ | 273 | [ |
| 274 | "{68A1E0E1-7A39-4AB6-AD71-4D8A18878D79},1", | 274 | "{42010094-E377-4A97-9395-AD45808D0552},1", |
| 275 | 1 | 275 | 1 |
| 276 | ], | 276 | ], |
| 277 | [ | 277 | [ |
| 278 | "{7E9041BD-301B-4B5B-9456-1C92050E4914},1", | 278 | "{0F6ABB5E-A021-4E1D-B187-7A72E74000DC},1", |
| 279 | 1 | 279 | 1 |
| 280 | ], | 280 | ], |
| 281 | [ | 281 | [ |
| 282 | "{EFA77BAE-8A98-4176-B536-B615D1D2ED2B},1", | 282 | "{F9DA3139-5856-4B11-95C0-356683E3ACFE},1", |
| 283 | 1 | 283 | 1 |
| 284 | ], | 284 | ], |
| 285 | [ | 285 | [ |
| 286 | "{87AC0239-51A5-47C6-BAF4-926CCF56385C},1", | 286 | "{439B80E0-9D4C-4A2F-9163-4E301491364D},1", |
| 287 | 1 | 287 | 1 |
| 288 | ], | 288 | ], |
| 289 | [ | 289 | [ |
| 290 | "{C50BF7FC-F848-4B91-B8C8-743D0AA5F195},1", | 290 | "{B03B973C-50BC-4B9A-9914-B1D5C0CF6197},1", |
| 291 | 1 | 291 | 1 |
| 292 | ], | 292 | ], |
| 293 | [ | 293 | [ |
| 294 | "{FD8D07D0-C447-4516-A7DB-F8522B77D4A7},1", | 294 | "{A459C474-E6EB-4BE5-9193-271799B2860B},1", |
| 295 | 1 | 295 | 1 |
| 296 | ], | 296 | ], |
| 297 | [ | 297 | [ |
| 298 | "{D2E1FE57-F45D-49C3-B388-95222ACA3886},1", | 298 | "{4ED14CEE-8560-4125-B488-9945C116741E},1", |
| 299 | 1 | 299 | 1 |
| 300 | ], | 300 | ], |
| 301 | [ | 301 | [ |
| 302 | "{7F6702B3-C977-436F-82AF-403E2BE950E7},1", | 302 | "{C4FF8FE4-4C43-461F-98DD-EB1FF2A863B2},1", |
| 303 | 1 | 303 | 1 |
| 304 | ], | 304 | ], |
| 305 | [ | 305 | [ |
| 306 | "{688C4EA1-80F5-4D85-B9BB-F8633FF3C0CA},1", | 306 | "{932BE37F-05AA-4EFB-85D8-A33445E738B3},1", |
| 307 | 1 | 307 | 1 |
| 308 | ], | 308 | ], |
| 309 | [ | 309 | [ |
| 310 | "{754FA957-C432-4502-A5CF-EE44449462C0},1", | 310 | "{9B755368-1D49-4D7E-8D33-6DC536F57848},1", |
| 311 | 1 | 311 | 1 |
| 312 | ], | 312 | ], |
| 313 | [ | 313 | [ |
| 314 | "{D2197C55-D962-4DF0-8B14-26F88C06D6FA},1", | 314 | "{D045EAAF-ACE9-4B4F-979B-E348A4ADE693},1", |
| 315 | 1 | 315 | 1 |
| 316 | ], | 316 | ], |
| 317 | [ | 317 | [ |
| 318 | "{87B83BA8-A906-4F71-A6CA-3E8133A75D34},1", | 318 | "{5CA4C59F-B400-4BC6-8B90-30ECAC35CB43},1", |
| 319 | 1 | 319 | 1 |
| 320 | ], | 320 | ], |
| 321 | [ | 321 | [ |
| 322 | "{F2D6AEBB-98EA-4F10-991F-5243E74AF450},1", | 322 | "{D63FB383-FB37-44F9-AEBB-E868E903A2BB},1", |
| 323 | 1 | 323 | 1 |
| 324 | ], | 324 | ], |
| 325 | [ | 325 | [ |
| 326 | "{406687D7-F9E9-491B-85DA-166DE6CD5A60},1", | 326 | "{C6E54364-6CD0-4764-8180-873030B9EA53},1", |
| 327 | 1 | 327 | 1 |
| 328 | ], | 328 | ], |
| 329 | [ | 329 | [ |
| 330 | "{1F73BF36-9FC2-495D-844D-B370D5C47DE7},1", | 330 | "{DEEC391B-F2D1-4EB1-BEB1-57598DFBB982},1", |
| 331 | 1 | 331 | 1 |
| 332 | ], | 332 | ], |
| 333 | [ | 333 | [ |
| 334 | "{E9BD343F-4A66-4DE2-81F9-3789CCA22818},1", | 334 | "{9E7616ED-DE19-4625-8EA0-0C5DEB18B690},1", |
| 335 | 1 | 335 | 1 |
| 336 | ], | 336 | ], |
| 337 | [ | 337 | [ |
| 338 | "{189EA744-BE13-467A-95BA-2667A134C0B0},1", | 338 | "{89F1748D-6749-4AD0-BAF6-07E9F9C058E5},1", |
| 339 | 1 | 339 | 1 |
| 340 | ], | 340 | ], |
| 341 | [ | 341 | [ |
| 342 | "{017F5881-F818-4C7C-B9F9-6BCB4085F1D0},1", | 342 | "{00B10C58-6649-428D-A081-3EBBA6E88E97},1", |
| 343 | 1 | 343 | 1 |
| 344 | ], | 344 | ], |
| 345 | [ | 345 | [ |
| 346 | "{CD0D17F4-25D8-4076-9AF7-47D9CDEFCF0A},1", | 346 | "{58E99482-710A-4938-8CFB-88A1C9B946C9},1", |
| 347 | 1 | 347 | 1 |
| 348 | ], | 348 | ], |
| 349 | [ | 349 | [ |
| 350 | "{CCBB5969-BCE8-498B-BE1A-98FD3A44DB1A},1", | 350 | "{F721C9D5-9A6A-405F-94C9-9B85A7EEB980},1", |
| 351 | 1 | 351 | 1 |
| 352 | ], | 352 | ], |
| 353 | [ | 353 | [ |
| 354 | "{2B736B19-2D9C-4E76-A732-478D533D717A},1", | 354 | "{15A45C01-A9E8-42E2-A267-C7FCE728815E},1", |
| 355 | 1 | 355 | 1 |
| 356 | ] | 356 | ] |
| 357 | ] | 357 | ] |
| 358 | } | 358 | } |
| 359 | }, | 359 | }, |
| 360 | "space": "{90FCDB0C-36AE-422F-B4FE-67676826FE36},1" | 360 | "space": "{A5A2AFBE-1DF1-4190-AAC7-1AD29C9564F2},1" |
| 361 | }, | 361 | }, |
| 362 | { | 362 | { |
| 363 | "id": 2, | 363 | "id": 2, |
| 364 | "operation": { | 364 | "operation": { |
| 365 | "Page": { | 365 | "Page": { |
| 366 | "after": { | 366 | "after": { |
| 367 | "created": 134335976560000000, | 367 | "created": 134348779890000000, |
| 368 | "definitions": { | 368 | "definitions": { |
| 369 | "{0E98EE82-B78F-4872-8BD9-5D9B23A817C5},18": { | 369 | "{DAFF533D-608B-4735-BA1D-2354E8CB8A69},18": { |
| 370 | "format": { | 370 | "format": { |
| 371 | "alignment": null, | 371 | "alignment": null, |
| 372 | "bold": null, | 372 | "bold": null, |
| ... | @@ -383,6 +383,7 @@ | ... | @@ -383,6 +383,7 @@ |
| 383 | "line_spacing": null, | 383 | "line_spacing": null, |
| 384 | "list_spacing": null, | 384 | "list_spacing": null, |
| 385 | "math": null, | 385 | "math": null, |
| 386 | "math_object": null, | ||
| 386 | "rtl": null, | 387 | "rtl": null, |
| 387 | "space_after": null, | 388 | "space_after": null, |
| 388 | "space_before": null, | 389 | "space_before": null, |
| ... | @@ -397,6 +398,24 @@ | ... | @@ -397,6 +398,24 @@ |
| 397 | } | 398 | } |
| 398 | } | 399 | } |
| 399 | }, | 400 | }, |
| 401 | "identity": [ | ||
| 402 | 61, | ||
| 403 | 83, | ||
| 404 | 255, | ||
| 405 | 218, | ||
| 406 | 139, | ||
| 407 | 96, | ||
| 408 | 53, | ||
| 409 | 71, | ||
| 410 | 186, | ||
| 411 | 29, | ||
| 412 | 35, | ||
| 413 | 84, | ||
| 414 | 232, | ||
| 415 | 203, | ||
| 416 | 138, | ||
| 417 | 105 | ||
| 418 | ], | ||
| 400 | "margin_origin": [ | 419 | "margin_origin": [ |
| 401 | 0.0, | 420 | 0.0, |
| 402 | 0.0 | 421 | 0.0 |
| ... | @@ -404,7 +423,7 @@ | ... | @@ -404,7 +423,7 @@ |
| 404 | "objects": [ | 423 | "objects": [ |
| 405 | { | 424 | { |
| 406 | "Outline": { | 425 | "Outline": { |
| 407 | "id": "{3517C5BC-B40B-45E6-8324-BAB9403F7F3B},0", | 426 | "id": "{91379CE6-91DA-48BA-B306-77E4CEB068B6},2", |
| 408 | "indents": [], | 427 | "indents": [], |
| 409 | "layout": { | 428 | "layout": { |
| 410 | "max_height": null, | 429 | "max_height": null, |
| ... | @@ -421,7 +440,7 @@ | ... | @@ -421,7 +440,7 @@ |
| 421 | "content": { | 440 | "content": { |
| 422 | "Text": { | 441 | "Text": { |
| 423 | "date_field": null, | 442 | "date_field": null, |
| 424 | "id": "{D76D17C9-C3D7-49A2-A78E-305604ABA8AF},0", | 443 | "id": "{91379CE6-91DA-48BA-B306-77E4CEB068B6},1", |
| 425 | "tags": [], | 444 | "tags": [], |
| 426 | "text": { | 445 | "text": { |
| 427 | "spans": [ | 446 | "spans": [ |
| ... | @@ -443,6 +462,7 @@ | ... | @@ -443,6 +462,7 @@ |
| 443 | "line_spacing": null, | 462 | "line_spacing": null, |
| 444 | "list_spacing": null, | 463 | "list_spacing": null, |
| 445 | "math": null, | 464 | "math": null, |
| 465 | "math_object": null, | ||
| 446 | "rtl": null, | 466 | "rtl": null, |
| 447 | "space_after": null, | 467 | "space_after": null, |
| 448 | "space_before": null, | 468 | "space_before": null, |
| ... | @@ -473,6 +493,7 @@ | ... | @@ -473,6 +493,7 @@ |
| 473 | "line_spacing": null, | 493 | "line_spacing": null, |
| 474 | "list_spacing": null, | 494 | "list_spacing": null, |
| 475 | "math": null, | 495 | "math": null, |
| 496 | "math_object": null, | ||
| 476 | "rtl": null, | 497 | "rtl": null, |
| 477 | "space_after": null, | 498 | "space_after": null, |
| 478 | "space_before": null, | 499 | "space_before": null, |
| ... | @@ -481,9 +502,13 @@ | ... | @@ -481,9 +502,13 @@ |
| 481 | "superscript": null, | 502 | "superscript": null, |
| 482 | "underline": null | 503 | "underline": null |
| 483 | }, | 504 | }, |
| 484 | "id": "{1A80E3C3-180F-42AB-B946-1A365F043826},0", | 505 | "id": "{91379CE6-91DA-48BA-B306-77E4CEB068B6},3", |
| 485 | "level": 1, | 506 | "level": 1, |
| 486 | "lists": [], | 507 | "lists": [], |
| 508 | "media": { | ||
| 509 | "recordings": [], | ||
| 510 | "time_ms": null | ||
| 511 | }, | ||
| 487 | "parent": null, | 512 | "parent": null, |
| 488 | "style": null, | 513 | "style": null, |
| 489 | "tags": [] | 514 | "tags": [] |
| ... | @@ -496,7 +521,7 @@ | ... | @@ -496,7 +521,7 @@ |
| 496 | { | 521 | { |
| 497 | "Title": { | 522 | "Title": { |
| 498 | "date": null, | 523 | "date": null, |
| 499 | "id": "{0E98EE82-B78F-4872-8BD9-5D9B23A817C5},13", | 524 | "id": "{DAFF533D-608B-4735-BA1D-2354E8CB8A69},13", |
| 500 | "layout": { | 525 | "layout": { |
| 501 | "max_height": null, | 526 | "max_height": null, |
| 502 | "max_width": null, | 527 | "max_width": null, |
| ... | @@ -507,24 +532,29 @@ | ... | @@ -507,24 +532,29 @@ |
| 507 | }, | 532 | }, |
| 508 | "outlines": [ | 533 | "outlines": [ |
| 509 | { | 534 | { |
| 510 | "id": "{0E98EE82-B78F-4872-8BD9-5D9B23A817C5},14", | 535 | "id": "{DAFF533D-608B-4735-BA1D-2354E8CB8A69},14", |
| 511 | "indents": [], | 536 | "indents": [ |
| 537 | 18.0, | ||
| 538 | 0.0, | ||
| 539 | 27.0, | ||
| 540 | 27.0 | ||
| 541 | ], | ||
| 512 | "layout": { | 542 | "layout": { |
| 513 | "max_height": null, | 543 | "max_height": 21.600000381469727, |
| 514 | "max_width": null, | 544 | "max_width": null, |
| 515 | "reserved_width": null, | 545 | "reserved_width": null, |
| 516 | "width_set_by_user": null, | 546 | "width_set_by_user": null, |
| 517 | "x": null, | 547 | "x": null, |
| 518 | "y": null | 548 | "y": null |
| 519 | }, | 549 | }, |
| 520 | "min_width": null, | 550 | "min_width": 162.0, |
| 521 | "paragraphs": [ | 551 | "paragraphs": [ |
| 522 | { | 552 | { |
| 523 | "collapsed": false, | 553 | "collapsed": false, |
| 524 | "content": { | 554 | "content": { |
| 525 | "Text": { | 555 | "Text": { |
| 526 | "date_field": null, | 556 | "date_field": null, |
| 527 | "id": "{0E98EE82-B78F-4872-8BD9-5D9B23A817C5},16", | 557 | "id": "{DAFF533D-608B-4735-BA1D-2354E8CB8A69},16", |
| 528 | "tags": [], | 558 | "tags": [], |
| 529 | "text": { | 559 | "text": { |
| 530 | "spans": [ | 560 | "spans": [ |
| ... | @@ -546,6 +576,7 @@ | ... | @@ -546,6 +576,7 @@ |
| 546 | "line_spacing": null, | 576 | "line_spacing": null, |
| 547 | "list_spacing": null, | 577 | "list_spacing": null, |
| 548 | "math": null, | 578 | "math": null, |
| 579 | "math_object": null, | ||
| 549 | "rtl": null, | 580 | "rtl": null, |
| 550 | "space_after": null, | 581 | "space_after": null, |
| 551 | "space_before": null, | 582 | "space_before": null, |
| ... | @@ -576,6 +607,7 @@ | ... | @@ -576,6 +607,7 @@ |
| 576 | "line_spacing": null, | 607 | "line_spacing": null, |
| 577 | "list_spacing": null, | 608 | "list_spacing": null, |
| 578 | "math": null, | 609 | "math": null, |
| 610 | "math_object": null, | ||
| 579 | "rtl": null, | 611 | "rtl": null, |
| 580 | "space_after": null, | 612 | "space_after": null, |
| 581 | "space_before": null, | 613 | "space_before": null, |
| ... | @@ -584,15 +616,19 @@ | ... | @@ -584,15 +616,19 @@ |
| 584 | "superscript": null, | 616 | "superscript": null, |
| 585 | "underline": null | 617 | "underline": null |
| 586 | }, | 618 | }, |
| 587 | "id": "{0E98EE82-B78F-4872-8BD9-5D9B23A817C5},15", | 619 | "id": "{DAFF533D-608B-4735-BA1D-2354E8CB8A69},15", |
| 588 | "level": 1, | 620 | "level": 1, |
| 589 | "lists": [], | 621 | "lists": [], |
| 622 | "media": { | ||
| 623 | "recordings": [], | ||
| 624 | "time_ms": null | ||
| 625 | }, | ||
| 590 | "parent": null, | 626 | "parent": null, |
| 591 | "style": "{0E98EE82-B78F-4872-8BD9-5D9B23A817C5},18", | 627 | "style": "{DAFF533D-608B-4735-BA1D-2354E8CB8A69},18", |
| 592 | "tags": [] | 628 | "tags": [] |
| 593 | } | 629 | } |
| 594 | ], | 630 | ], |
| 595 | "title": false, | 631 | "title": true, |
| 596 | "unsupported": [] | 632 | "unsupported": [] |
| 597 | } | 633 | } |
| 598 | ] | 634 | ] |
| ... | @@ -603,9 +639,9 @@ | ... | @@ -603,9 +639,9 @@ |
| 603 | }, | 639 | }, |
| 604 | "author": "Author", | 640 | "author": "Author", |
| 605 | "before": { | 641 | "before": { |
| 606 | "created": 134335976560000000, | 642 | "created": 134348779890000000, |
| 607 | "definitions": { | 643 | "definitions": { |
| 608 | "{0E98EE82-B78F-4872-8BD9-5D9B23A817C5},18": { | 644 | "{DAFF533D-608B-4735-BA1D-2354E8CB8A69},18": { |
| 609 | "format": { | 645 | "format": { |
| 610 | "alignment": null, | 646 | "alignment": null, |
| 611 | "bold": null, | 647 | "bold": null, |
| ... | @@ -622,6 +658,7 @@ | ... | @@ -622,6 +658,7 @@ |
| 622 | "line_spacing": null, | 658 | "line_spacing": null, |
| 623 | "list_spacing": null, | 659 | "list_spacing": null, |
| 624 | "math": null, | 660 | "math": null, |
| 661 | "math_object": null, | ||
| 625 | "rtl": null, | 662 | "rtl": null, |
| 626 | "space_after": null, | 663 | "space_after": null, |
| 627 | "space_before": null, | 664 | "space_before": null, |
| ... | @@ -636,6 +673,24 @@ | ... | @@ -636,6 +673,24 @@ |
| 636 | } | 673 | } |
| 637 | } | 674 | } |
| 638 | }, | 675 | }, |
| 676 | "identity": [ | ||
| 677 | 61, | ||
| 678 | 83, | ||
| 679 | 255, | ||
| 680 | 218, | ||
| 681 | 139, | ||
| 682 | 96, | ||
| 683 | 53, | ||
| 684 | 71, | ||
| 685 | 186, | ||
| 686 | 29, | ||
| 687 | 35, | ||
| 688 | 84, | ||
| 689 | 232, | ||
| 690 | 203, | ||
| 691 | 138, | ||
| 692 | 105 | ||
| 693 | ], | ||
| 639 | "margin_origin": [ | 694 | "margin_origin": [ |
| 640 | 0.0, | 695 | 0.0, |
| 641 | 0.0 | 696 | 0.0 |
| ... | @@ -644,7 +699,7 @@ | ... | @@ -644,7 +699,7 @@ |
| 644 | { | 699 | { |
| 645 | "Title": { | 700 | "Title": { |
| 646 | "date": null, | 701 | "date": null, |
| 647 | "id": "{0E98EE82-B78F-4872-8BD9-5D9B23A817C5},13", | 702 | "id": "{DAFF533D-608B-4735-BA1D-2354E8CB8A69},13", |
| 648 | "layout": { | 703 | "layout": { |
| 649 | "max_height": null, | 704 | "max_height": null, |
| 650 | "max_width": null, | 705 | "max_width": null, |
| ... | @@ -655,24 +710,29 @@ | ... | @@ -655,24 +710,29 @@ |
| 655 | }, | 710 | }, |
| 656 | "outlines": [ | 711 | "outlines": [ |
| 657 | { | 712 | { |
| 658 | "id": "{0E98EE82-B78F-4872-8BD9-5D9B23A817C5},14", | 713 | "id": "{DAFF533D-608B-4735-BA1D-2354E8CB8A69},14", |
| 659 | "indents": [], | 714 | "indents": [ |
| 715 | 18.0, | ||
| 716 | 0.0, | ||
| 717 | 27.0, | ||
| 718 | 27.0 | ||
| 719 | ], | ||
| 660 | "layout": { | 720 | "layout": { |
| 661 | "max_height": null, | 721 | "max_height": 21.600000381469727, |
| 662 | "max_width": null, | 722 | "max_width": null, |
| 663 | "reserved_width": null, | 723 | "reserved_width": null, |
| 664 | "width_set_by_user": null, | 724 | "width_set_by_user": null, |
| 665 | "x": null, | 725 | "x": null, |
| 666 | "y": null | 726 | "y": null |
| 667 | }, | 727 | }, |
| 668 | "min_width": null, | 728 | "min_width": 162.0, |
| 669 | "paragraphs": [ | 729 | "paragraphs": [ |
| 670 | { | 730 | { |
| 671 | "collapsed": false, | 731 | "collapsed": false, |
| 672 | "content": { | 732 | "content": { |
| 673 | "Text": { | 733 | "Text": { |
| 674 | "date_field": null, | 734 | "date_field": null, |
| 675 | "id": "{0E98EE82-B78F-4872-8BD9-5D9B23A817C5},16", | 735 | "id": "{DAFF533D-608B-4735-BA1D-2354E8CB8A69},16", |
| 676 | "tags": [], | 736 | "tags": [], |
| 677 | "text": { | 737 | "text": { |
| 678 | "spans": [ | 738 | "spans": [ |
| ... | @@ -694,6 +754,7 @@ | ... | @@ -694,6 +754,7 @@ |
| 694 | "line_spacing": null, | 754 | "line_spacing": null, |
| 695 | "list_spacing": null, | 755 | "list_spacing": null, |
| 696 | "math": null, | 756 | "math": null, |
| 757 | "math_object": null, | ||
| 697 | "rtl": null, | 758 | "rtl": null, |
| 698 | "space_after": null, | 759 | "space_after": null, |
| 699 | "space_before": null, | 760 | "space_before": null, |
| ... | @@ -724,6 +785,7 @@ | ... | @@ -724,6 +785,7 @@ |
| 724 | "line_spacing": null, | 785 | "line_spacing": null, |
| 725 | "list_spacing": null, | 786 | "list_spacing": null, |
| 726 | "math": null, | 787 | "math": null, |
| 788 | "math_object": null, | ||
| 727 | "rtl": null, | 789 | "rtl": null, |
| 728 | "space_after": null, | 790 | "space_after": null, |
| 729 | "space_before": null, | 791 | "space_before": null, |
| ... | @@ -732,15 +794,19 @@ | ... | @@ -732,15 +794,19 @@ |
| 732 | "superscript": null, | 794 | "superscript": null, |
| 733 | "underline": null | 795 | "underline": null |
| 734 | }, | 796 | }, |
| 735 | "id": "{0E98EE82-B78F-4872-8BD9-5D9B23A817C5},15", | 797 | "id": "{DAFF533D-608B-4735-BA1D-2354E8CB8A69},15", |
| 736 | "level": 1, | 798 | "level": 1, |
| 737 | "lists": [], | 799 | "lists": [], |
| 800 | "media": { | ||
| 801 | "recordings": [], | ||
| 802 | "time_ms": null | ||
| 803 | }, | ||
| 738 | "parent": null, | 804 | "parent": null, |
| 739 | "style": "{0E98EE82-B78F-4872-8BD9-5D9B23A817C5},18", | 805 | "style": "{DAFF533D-608B-4735-BA1D-2354E8CB8A69},18", |
| 740 | "tags": [] | 806 | "tags": [] |
| 741 | } | 807 | } |
| 742 | ], | 808 | ], |
| 743 | "title": false, | 809 | "title": true, |
| 744 | "unsupported": [] | 810 | "unsupported": [] |
| 745 | } | 811 | } |
| 746 | ] | 812 | ] |
| ... | @@ -751,7 +817,7 @@ | ... | @@ -751,7 +817,7 @@ |
| 751 | } | 817 | } |
| 752 | } | 818 | } |
| 753 | }, | 819 | }, |
| 754 | "space": "{0E98EE82-B78F-4872-8BD9-5D9B23A817C5},1" | 820 | "space": "{DAFF533D-608B-4735-BA1D-2354E8CB8A69},1" |
| 755 | } | 821 | } |
| 756 | ], | 822 | ], |
| 757 | [ | 823 | [ |
| ... | @@ -762,215 +828,215 @@ | ... | @@ -762,215 +828,215 @@ |
| 762 | "edits": [ | 828 | "edits": [ |
| 763 | { | 829 | { |
| 764 | "guid": [ | 830 | "guid": [ |
| 765 | 183, | 831 | 17, |
| 766 | 11, | ||
| 767 | 130, | ||
| 768 | 190, | ||
| 769 | 141, | ||
| 770 | 83, | ||
| 771 | 119, | ||
| 772 | 77, | ||
| 773 | 133, | ||
| 774 | 112, | ||
| 775 | 123, | ||
| 776 | 250, | ||
| 777 | 37, | ||
| 778 | 199, | ||
| 779 | 26, | 832 | 26, |
| 780 | 252 | 833 | 44, |
| 834 | 166, | ||
| 835 | 129, | ||
| 836 | 151, | ||
| 837 | 120, | ||
| 838 | 77, | ||
| 839 | 170, | ||
| 840 | 191, | ||
| 841 | 124, | ||
| 842 | 194, | ||
| 843 | 153, | ||
| 844 | 11, | ||
| 845 | 230, | ||
| 846 | 226 | ||
| 781 | ], | 847 | ], |
| 782 | "level": 1, | 848 | "level": 1, |
| 783 | "position": { | 849 | "position": { |
| 784 | "Before": "{89D86121-BF5C-418B-BB92-CBFFA8EF545F},1" | 850 | "Before": "{28E0CC5F-68BC-4A86-9ED7-114FFD3BAE9A},1" |
| 785 | }, | 851 | }, |
| 786 | "space": "{57E835EA-2799-4118-B55C-7BB1E51C6941},1" | 852 | "space": "{31FFA5F9-C1B8-412D-A2D6-7ACC3CC16FD0},1" |
| 787 | }, | 853 | }, |
| 788 | { | 854 | { |
| 789 | "guid": [ | 855 | "guid": [ |
| 790 | 252, | 856 | 6, |
| 791 | 97, | 857 | 151, |
| 792 | 42, | 858 | 55, |
| 793 | 186, | 859 | 70, |
| 794 | 167, | 860 | 228, |
| 795 | 236, | 861 | 106, |
| 796 | 97, | 862 | 247, |
| 863 | 64, | ||
| 864 | 149, | ||
| 865 | 253, | ||
| 866 | 46, | ||
| 867 | 250, | ||
| 868 | 242, | ||
| 869 | 9, | ||
| 797 | 65, | 870 | 65, |
| 798 | 143, | 871 | 14 |
| 799 | 137, | ||
| 800 | 54, | ||
| 801 | 20, | ||
| 802 | 165, | ||
| 803 | 72, | ||
| 804 | 170, | ||
| 805 | 170 | ||
| 806 | ], | 872 | ], |
| 807 | "level": 2, | 873 | "level": 2, |
| 808 | "position": "Keep", | 874 | "position": "Keep", |
| 809 | "space": "{F7EECCCF-B6B4-44F2-A543-36837EC18B8A},1" | 875 | "space": "{AFCD09F6-1085-4582-BDB2-4B91E15D2088},1" |
| 810 | } | 876 | } |
| 811 | ], | 877 | ], |
| 812 | "observed": [ | 878 | "observed": [ |
| 813 | [ | 879 | [ |
| 814 | "{869D5929-6E5E-4EDB-9639-4C56977DD447},1", | 880 | "{0B37E922-551A-48F8-BCAC-4AB3503B4D28},1", |
| 815 | 1 | 881 | 1 |
| 816 | ], | 882 | ], |
| 817 | [ | 883 | [ |
| 818 | "{B0B53F94-0105-4C59-B65B-9473F49BFF41},1", | 884 | "{74492C4A-164B-40E9-B297-4D3B4685347D},1", |
| 819 | 1 | 885 | 1 |
| 820 | ], | 886 | ], |
| 821 | [ | 887 | [ |
| 822 | "{0E98EE82-B78F-4872-8BD9-5D9B23A817C5},1", | 888 | "{DAFF533D-608B-4735-BA1D-2354E8CB8A69},1", |
| 823 | 1 | 889 | 1 |
| 824 | ], | 890 | ], |
| 825 | [ | 891 | [ |
| 826 | "{C837A033-F055-4455-B45F-17D82016921C},1", | 892 | "{8B766F11-2A4F-4D95-8473-D3CA68129E11},1", |
| 827 | 1 | 893 | 1 |
| 828 | ], | 894 | ], |
| 829 | [ | 895 | [ |
| 830 | "{89D86121-BF5C-418B-BB92-CBFFA8EF545F},1", | 896 | "{28E0CC5F-68BC-4A86-9ED7-114FFD3BAE9A},1", |
| 831 | 1 | 897 | 1 |
| 832 | ], | 898 | ], |
| 833 | [ | 899 | [ |
| 834 | "{57E835EA-2799-4118-B55C-7BB1E51C6941},1", | 900 | "{31FFA5F9-C1B8-412D-A2D6-7ACC3CC16FD0},1", |
| 835 | 1 | 901 | 1 |
| 836 | ], | 902 | ], |
| 837 | [ | 903 | [ |
| 838 | "{F7EECCCF-B6B4-44F2-A543-36837EC18B8A},1", | 904 | "{AFCD09F6-1085-4582-BDB2-4B91E15D2088},1", |
| 839 | 1 | 905 | 1 |
| 840 | ], | 906 | ], |
| 841 | [ | 907 | [ |
| 842 | "{361A0F09-EA63-4F2D-9620-C061D4E4707E},1", | 908 | "{A94D5882-C902-4CAE-B38E-2ED56EEF177F},1", |
| 843 | 1 | 909 | 1 |
| 844 | ], | 910 | ], |
| 845 | [ | 911 | [ |
| 846 | "{EB3940F1-F255-42BE-8C31-DD09E86DBAAF},1", | 912 | "{5AB37A0F-5702-4C8E-A8C6-79ED6ED1E69F},1", |
| 847 | 1 | 913 | 1 |
| 848 | ], | 914 | ], |
| 849 | [ | 915 | [ |
| 850 | "{C2870D07-8ADC-44BB-9F12-15D6C3708689},1", | 916 | "{F212795E-0E4B-4844-A8A6-ABD7F7025124},1", |
| 851 | 1 | 917 | 1 |
| 852 | ], | 918 | ], |
| 853 | [ | 919 | [ |
| 854 | "{1E0C6331-32C9-4EF9-8087-150A8669FA91},1", | 920 | "{1786F2CA-0A43-4081-8B55-B207EEE0331C},1", |
| 855 | 1 | 921 | 1 |
| 856 | ], | 922 | ], |
| 857 | [ | 923 | [ |
| 858 | "{ED9769DD-99D6-4BE2-B980-580DA9C4DFCE},1", | 924 | "{3E966EF8-B7B4-483F-B241-8F21DF318AB3},1", |
| 859 | 1 | 925 | 1 |
| 860 | ], | 926 | ], |
| 861 | [ | 927 | [ |
| 862 | "{17C30054-7129-4A10-9D00-4D5545E4F4BA},1", | 928 | "{0D361C76-92FD-4B76-901B-809C2C3C3CE6},1", |
| 863 | 1 | 929 | 1 |
| 864 | ], | 930 | ], |
| 865 | [ | 931 | [ |
| 866 | "{4C5FBA5B-E348-4750-ACE4-E9694D527BFF},1", | 932 | "{AE89305A-73F4-46B5-B41D-18884685D896},1", |
| 867 | 1 | 933 | 1 |
| 868 | ], | 934 | ], |
| 869 | [ | 935 | [ |
| 870 | "{BD0EF658-4D0F-42A7-B546-B4F4A77D246B},1", | 936 | "{551D2637-2AA7-4923-B2DD-31C2E1489FD2},1", |
| 871 | 1 | 937 | 1 |
| 872 | ], | 938 | ], |
| 873 | [ | 939 | [ |
| 874 | "{6FEA70FA-1451-4A27-8A8E-C4CC3B713CF6},1", | 940 | "{CBF82C86-3A8B-457F-92A4-1AC7BF436CE9},1", |
| 875 | 1 | 941 | 1 |
| 876 | ], | 942 | ], |
| 877 | [ | 943 | [ |
| 878 | "{68A1E0E1-7A39-4AB6-AD71-4D8A18878D79},1", | 944 | "{42010094-E377-4A97-9395-AD45808D0552},1", |
| 879 | 1 | 945 | 1 |
| 880 | ], | 946 | ], |
| 881 | [ | 947 | [ |
| 882 | "{7E9041BD-301B-4B5B-9456-1C92050E4914},1", | 948 | "{0F6ABB5E-A021-4E1D-B187-7A72E74000DC},1", |
| 883 | 1 | 949 | 1 |
| 884 | ], | 950 | ], |
| 885 | [ | 951 | [ |
| 886 | "{EFA77BAE-8A98-4176-B536-B615D1D2ED2B},1", | 952 | "{F9DA3139-5856-4B11-95C0-356683E3ACFE},1", |
| 887 | 1 | 953 | 1 |
| 888 | ], | 954 | ], |
| 889 | [ | 955 | [ |
| 890 | "{87AC0239-51A5-47C6-BAF4-926CCF56385C},1", | 956 | "{439B80E0-9D4C-4A2F-9163-4E301491364D},1", |
| 891 | 1 | 957 | 1 |
| 892 | ], | 958 | ], |
| 893 | [ | 959 | [ |
| 894 | "{C50BF7FC-F848-4B91-B8C8-743D0AA5F195},1", | 960 | "{B03B973C-50BC-4B9A-9914-B1D5C0CF6197},1", |
| 895 | 1 | 961 | 1 |
| 896 | ], | 962 | ], |
| 897 | [ | 963 | [ |
| 898 | "{FD8D07D0-C447-4516-A7DB-F8522B77D4A7},1", | 964 | "{A459C474-E6EB-4BE5-9193-271799B2860B},1", |
| 899 | 1 | 965 | 1 |
| 900 | ], | 966 | ], |
| 901 | [ | 967 | [ |
| 902 | "{D2E1FE57-F45D-49C3-B388-95222ACA3886},1", | 968 | "{4ED14CEE-8560-4125-B488-9945C116741E},1", |
| 903 | 1 | 969 | 1 |
| 904 | ], | 970 | ], |
| 905 | [ | 971 | [ |
| 906 | "{7F6702B3-C977-436F-82AF-403E2BE950E7},1", | 972 | "{C4FF8FE4-4C43-461F-98DD-EB1FF2A863B2},1", |
| 907 | 1 | 973 | 1 |
| 908 | ], | 974 | ], |
| 909 | [ | 975 | [ |
| 910 | "{688C4EA1-80F5-4D85-B9BB-F8633FF3C0CA},1", | 976 | "{932BE37F-05AA-4EFB-85D8-A33445E738B3},1", |
| 911 | 1 | 977 | 1 |
| 912 | ], | 978 | ], |
| 913 | [ | 979 | [ |
| 914 | "{754FA957-C432-4502-A5CF-EE44449462C0},1", | 980 | "{9B755368-1D49-4D7E-8D33-6DC536F57848},1", |
| 915 | 1 | 981 | 1 |
| 916 | ], | 982 | ], |
| 917 | [ | 983 | [ |
| 918 | "{D2197C55-D962-4DF0-8B14-26F88C06D6FA},1", | 984 | "{D045EAAF-ACE9-4B4F-979B-E348A4ADE693},1", |
| 919 | 1 | 985 | 1 |
| 920 | ], | 986 | ], |
| 921 | [ | 987 | [ |
| 922 | "{87B83BA8-A906-4F71-A6CA-3E8133A75D34},1", | 988 | "{5CA4C59F-B400-4BC6-8B90-30ECAC35CB43},1", |
| 923 | 1 | 989 | 1 |
| 924 | ], | 990 | ], |
| 925 | [ | 991 | [ |
| 926 | "{F2D6AEBB-98EA-4F10-991F-5243E74AF450},1", | 992 | "{D63FB383-FB37-44F9-AEBB-E868E903A2BB},1", |
| 927 | 1 | 993 | 1 |
| 928 | ], | 994 | ], |
| 929 | [ | 995 | [ |
| 930 | "{406687D7-F9E9-491B-85DA-166DE6CD5A60},1", | 996 | "{C6E54364-6CD0-4764-8180-873030B9EA53},1", |
| 931 | 1 | 997 | 1 |
| 932 | ], | 998 | ], |
| 933 | [ | 999 | [ |
| 934 | "{1F73BF36-9FC2-495D-844D-B370D5C47DE7},1", | 1000 | "{DEEC391B-F2D1-4EB1-BEB1-57598DFBB982},1", |
| 935 | 1 | 1001 | 1 |
| 936 | ], | 1002 | ], |
| 937 | [ | 1003 | [ |
| 938 | "{E9BD343F-4A66-4DE2-81F9-3789CCA22818},1", | 1004 | "{9E7616ED-DE19-4625-8EA0-0C5DEB18B690},1", |
| 939 | 1 | 1005 | 1 |
| 940 | ], | 1006 | ], |
| 941 | [ | 1007 | [ |
| 942 | "{189EA744-BE13-467A-95BA-2667A134C0B0},1", | 1008 | "{89F1748D-6749-4AD0-BAF6-07E9F9C058E5},1", |
| 943 | 1 | 1009 | 1 |
| 944 | ], | 1010 | ], |
| 945 | [ | 1011 | [ |
| 946 | "{017F5881-F818-4C7C-B9F9-6BCB4085F1D0},1", | 1012 | "{00B10C58-6649-428D-A081-3EBBA6E88E97},1", |
| 947 | 1 | 1013 | 1 |
| 948 | ], | 1014 | ], |
| 949 | [ | 1015 | [ |
| 950 | "{CD0D17F4-25D8-4076-9AF7-47D9CDEFCF0A},1", | 1016 | "{58E99482-710A-4938-8CFB-88A1C9B946C9},1", |
| 951 | 1 | 1017 | 1 |
| 952 | ], | 1018 | ], |
| 953 | [ | 1019 | [ |
| 954 | "{CCBB5969-BCE8-498B-BE1A-98FD3A44DB1A},1", | 1020 | "{F721C9D5-9A6A-405F-94C9-9B85A7EEB980},1", |
| 955 | 1 | 1021 | 1 |
| 956 | ], | 1022 | ], |
| 957 | [ | 1023 | [ |
| 958 | "{2B736B19-2D9C-4E76-A732-478D533D717A},1", | 1024 | "{15A45C01-A9E8-42E2-A267-C7FCE728815E},1", |
| 959 | 1 | 1025 | 1 |
| 960 | ] | 1026 | ] |
| 961 | ] | 1027 | ] |
| 962 | } | 1028 | } |
| 963 | }, | 1029 | }, |
| 964 | "space": "{90FCDB0C-36AE-422F-B4FE-67676826FE36},1" | 1030 | "space": "{A5A2AFBE-1DF1-4190-AAC7-1AD29C9564F2},1" |
| 965 | }, | 1031 | }, |
| 966 | { | 1032 | { |
| 967 | "id": 2, | 1033 | "id": 2, |
| 968 | "operation": { | 1034 | "operation": { |
| 969 | "Page": { | 1035 | "Page": { |
| 970 | "after": { | 1036 | "after": { |
| 971 | "created": 134335976560000000, | 1037 | "created": 134348779890000000, |
| 972 | "definitions": { | 1038 | "definitions": { |
| 973 | "{57E835EA-2799-4118-B55C-7BB1E51C6941},18": { | 1039 | "{31FFA5F9-C1B8-412D-A2D6-7ACC3CC16FD0},18": { |
| 974 | "format": { | 1040 | "format": { |
| 975 | "alignment": null, | 1041 | "alignment": null, |
| 976 | "bold": null, | 1042 | "bold": null, |
| ... | @@ -987,6 +1053,7 @@ | ... | @@ -987,6 +1053,7 @@ |
| 987 | "line_spacing": null, | 1053 | "line_spacing": null, |
| 988 | "list_spacing": null, | 1054 | "list_spacing": null, |
| 989 | "math": null, | 1055 | "math": null, |
| 1056 | "math_object": null, | ||
| 990 | "rtl": null, | 1057 | "rtl": null, |
| 991 | "space_after": null, | 1058 | "space_after": null, |
| 992 | "space_before": null, | 1059 | "space_before": null, |
| ... | @@ -1001,6 +1068,24 @@ | ... | @@ -1001,6 +1068,24 @@ |
| 1001 | } | 1068 | } |
| 1002 | } | 1069 | } |
| 1003 | }, | 1070 | }, |
| 1071 | "identity": [ | ||
| 1072 | 249, | ||
| 1073 | 165, | ||
| 1074 | 255, | ||
| 1075 | 49, | ||
| 1076 | 184, | ||
| 1077 | 193, | ||
| 1078 | 45, | ||
| 1079 | 65, | ||
| 1080 | 162, | ||
| 1081 | 214, | ||
| 1082 | 122, | ||
| 1083 | 204, | ||
| 1084 | 60, | ||
| 1085 | 193, | ||
| 1086 | 111, | ||
| 1087 | 208 | ||
| 1088 | ], | ||
| 1004 | "margin_origin": [ | 1089 | "margin_origin": [ |
| 1005 | 0.0, | 1090 | 0.0, |
| 1006 | 0.0 | 1091 | 0.0 |
| ... | @@ -1008,7 +1093,7 @@ | ... | @@ -1008,7 +1093,7 @@ |
| 1008 | "objects": [ | 1093 | "objects": [ |
| 1009 | { | 1094 | { |
| 1010 | "Outline": { | 1095 | "Outline": { |
| 1011 | "id": "{0817794A-7E1B-41EF-A811-A0F65EE1CE99},0", | 1096 | "id": "{91379CE6-91DA-48BA-B306-77E4CEB068B6},5", |
| 1012 | "indents": [], | 1097 | "indents": [], |
| 1013 | "layout": { | 1098 | "layout": { |
| 1014 | "max_height": null, | 1099 | "max_height": null, |
| ... | @@ -1025,7 +1110,7 @@ | ... | @@ -1025,7 +1110,7 @@ |
| 1025 | "content": { | 1110 | "content": { |
| 1026 | "Text": { | 1111 | "Text": { |
| 1027 | "date_field": null, | 1112 | "date_field": null, |
| 1028 | "id": "{BB519837-B3F7-4C9B-A428-3D9696CA0070},0", | 1113 | "id": "{91379CE6-91DA-48BA-B306-77E4CEB068B6},4", |
| 1029 | "tags": [], | 1114 | "tags": [], |
| 1030 | "text": { | 1115 | "text": { |
| 1031 | "spans": [ | 1116 | "spans": [ |
| ... | @@ -1047,6 +1132,7 @@ | ... | @@ -1047,6 +1132,7 @@ |
| 1047 | "line_spacing": null, | 1132 | "line_spacing": null, |
| 1048 | "list_spacing": null, | 1133 | "list_spacing": null, |
| 1049 | "math": null, | 1134 | "math": null, |
| 1135 | "math_object": null, | ||
| 1050 | "rtl": null, | 1136 | "rtl": null, |
| 1051 | "space_after": null, | 1137 | "space_after": null, |
| 1052 | "space_before": null, | 1138 | "space_before": null, |
| ... | @@ -1077,6 +1163,7 @@ | ... | @@ -1077,6 +1163,7 @@ |
| 1077 | "line_spacing": null, | 1163 | "line_spacing": null, |
| 1078 | "list_spacing": null, | 1164 | "list_spacing": null, |
| 1079 | "math": null, | 1165 | "math": null, |
| 1166 | "math_object": null, | ||
| 1080 | "rtl": null, | 1167 | "rtl": null, |
| 1081 | "space_after": null, | 1168 | "space_after": null, |
| 1082 | "space_before": null, | 1169 | "space_before": null, |
| ... | @@ -1085,9 +1172,13 @@ | ... | @@ -1085,9 +1172,13 @@ |
| 1085 | "superscript": null, | 1172 | "superscript": null, |
| 1086 | "underline": null | 1173 | "underline": null |
| 1087 | }, | 1174 | }, |
| 1088 | "id": "{88AA1A5C-2020-4249-BF25-62D5B4957CC6},0", | 1175 | "id": "{91379CE6-91DA-48BA-B306-77E4CEB068B6},6", |
| 1089 | "level": 1, | 1176 | "level": 1, |
| 1090 | "lists": [], | 1177 | "lists": [], |
| 1178 | "media": { | ||
| 1179 | "recordings": [], | ||
| 1180 | "time_ms": null | ||
| 1181 | }, | ||
| 1091 | "parent": null, | 1182 | "parent": null, |
| 1092 | "style": null, | 1183 | "style": null, |
| 1093 | "tags": [] | 1184 | "tags": [] |
| ... | @@ -1100,7 +1191,7 @@ | ... | @@ -1100,7 +1191,7 @@ |
| 1100 | { | 1191 | { |
| 1101 | "Title": { | 1192 | "Title": { |
| 1102 | "date": null, | 1193 | "date": null, |
| 1103 | "id": "{57E835EA-2799-4118-B55C-7BB1E51C6941},13", | 1194 | "id": "{31FFA5F9-C1B8-412D-A2D6-7ACC3CC16FD0},13", |
| 1104 | "layout": { | 1195 | "layout": { |
| 1105 | "max_height": null, | 1196 | "max_height": null, |
| 1106 | "max_width": null, | 1197 | "max_width": null, |
| ... | @@ -1111,24 +1202,29 @@ | ... | @@ -1111,24 +1202,29 @@ |
| 1111 | }, | 1202 | }, |
| 1112 | "outlines": [ | 1203 | "outlines": [ |
| 1113 | { | 1204 | { |
| 1114 | "id": "{57E835EA-2799-4118-B55C-7BB1E51C6941},14", | 1205 | "id": "{31FFA5F9-C1B8-412D-A2D6-7ACC3CC16FD0},14", |
| 1115 | "indents": [], | 1206 | "indents": [ |
| 1207 | 18.0, | ||
| 1208 | 0.0, | ||
| 1209 | 27.0, | ||
| 1210 | 27.0 | ||
| 1211 | ], | ||
| 1116 | "layout": { | 1212 | "layout": { |
| 1117 | "max_height": null, | 1213 | "max_height": 21.600000381469727, |
| 1118 | "max_width": null, | 1214 | "max_width": null, |
| 1119 | "reserved_width": null, | 1215 | "reserved_width": null, |
| 1120 | "width_set_by_user": null, | 1216 | "width_set_by_user": null, |
| 1121 | "x": null, | 1217 | "x": null, |
| 1122 | "y": null | 1218 | "y": null |
| 1123 | }, | 1219 | }, |
| 1124 | "min_width": null, | 1220 | "min_width": 162.0, |
| 1125 | "paragraphs": [ | 1221 | "paragraphs": [ |
| 1126 | { | 1222 | { |
| 1127 | "collapsed": false, | 1223 | "collapsed": false, |
| 1128 | "content": { | 1224 | "content": { |
| 1129 | "Text": { | 1225 | "Text": { |
| 1130 | "date_field": null, | 1226 | "date_field": null, |
| 1131 | "id": "{57E835EA-2799-4118-B55C-7BB1E51C6941},16", | 1227 | "id": "{31FFA5F9-C1B8-412D-A2D6-7ACC3CC16FD0},16", |
| 1132 | "tags": [], | 1228 | "tags": [], |
| 1133 | "text": { | 1229 | "text": { |
| 1134 | "spans": [ | 1230 | "spans": [ |
| ... | @@ -1150,6 +1246,7 @@ | ... | @@ -1150,6 +1246,7 @@ |
| 1150 | "line_spacing": null, | 1246 | "line_spacing": null, |
| 1151 | "list_spacing": null, | 1247 | "list_spacing": null, |
| 1152 | "math": null, | 1248 | "math": null, |
| 1249 | "math_object": null, | ||
| 1153 | "rtl": null, | 1250 | "rtl": null, |
| 1154 | "space_after": null, | 1251 | "space_after": null, |
| 1155 | "space_before": null, | 1252 | "space_before": null, |
| ... | @@ -1180,6 +1277,7 @@ | ... | @@ -1180,6 +1277,7 @@ |
| 1180 | "line_spacing": null, | 1277 | "line_spacing": null, |
| 1181 | "list_spacing": null, | 1278 | "list_spacing": null, |
| 1182 | "math": null, | 1279 | "math": null, |
| 1280 | "math_object": null, | ||
| 1183 | "rtl": null, | 1281 | "rtl": null, |
| 1184 | "space_after": null, | 1282 | "space_after": null, |
| 1185 | "space_before": null, | 1283 | "space_before": null, |
| ... | @@ -1188,15 +1286,19 @@ | ... | @@ -1188,15 +1286,19 @@ |
| 1188 | "superscript": null, | 1286 | "superscript": null, |
| 1189 | "underline": null | 1287 | "underline": null |
| 1190 | }, | 1288 | }, |
| 1191 | "id": "{57E835EA-2799-4118-B55C-7BB1E51C6941},15", | 1289 | "id": "{31FFA5F9-C1B8-412D-A2D6-7ACC3CC16FD0},15", |
| 1192 | "level": 1, | 1290 | "level": 1, |
| 1193 | "lists": [], | 1291 | "lists": [], |
| 1292 | "media": { | ||
| 1293 | "recordings": [], | ||
| 1294 | "time_ms": null | ||
| 1295 | }, | ||
| 1194 | "parent": null, | 1296 | "parent": null, |
| 1195 | "style": "{57E835EA-2799-4118-B55C-7BB1E51C6941},18", | 1297 | "style": "{31FFA5F9-C1B8-412D-A2D6-7ACC3CC16FD0},18", |
| 1196 | "tags": [] | 1298 | "tags": [] |
| 1197 | } | 1299 | } |
| 1198 | ], | 1300 | ], |
| 1199 | "title": false, | 1301 | "title": true, |
| 1200 | "unsupported": [] | 1302 | "unsupported": [] |
| 1201 | } | 1303 | } |
| 1202 | ] | 1304 | ] |
| ... | @@ -1207,9 +1309,9 @@ | ... | @@ -1207,9 +1309,9 @@ |
| 1207 | }, | 1309 | }, |
| 1208 | "author": "Author", | 1310 | "author": "Author", |
| 1209 | "before": { | 1311 | "before": { |
| 1210 | "created": 134335976560000000, | 1312 | "created": 134348779890000000, |
| 1211 | "definitions": { | 1313 | "definitions": { |
| 1212 | "{57E835EA-2799-4118-B55C-7BB1E51C6941},18": { | 1314 | "{31FFA5F9-C1B8-412D-A2D6-7ACC3CC16FD0},18": { |
| 1213 | "format": { | 1315 | "format": { |
| 1214 | "alignment": null, | 1316 | "alignment": null, |
| 1215 | "bold": null, | 1317 | "bold": null, |
| ... | @@ -1226,6 +1328,7 @@ | ... | @@ -1226,6 +1328,7 @@ |
| 1226 | "line_spacing": null, | 1328 | "line_spacing": null, |
| 1227 | "list_spacing": null, | 1329 | "list_spacing": null, |
| 1228 | "math": null, | 1330 | "math": null, |
| 1331 | "math_object": null, | ||
| 1229 | "rtl": null, | 1332 | "rtl": null, |
| 1230 | "space_after": null, | 1333 | "space_after": null, |
| 1231 | "space_before": null, | 1334 | "space_before": null, |
| ... | @@ -1240,6 +1343,24 @@ | ... | @@ -1240,6 +1343,24 @@ |
| 1240 | } | 1343 | } |
| 1241 | } | 1344 | } |
| 1242 | }, | 1345 | }, |
| 1346 | "identity": [ | ||
| 1347 | 249, | ||
| 1348 | 165, | ||
| 1349 | 255, | ||
| 1350 | 49, | ||
| 1351 | 184, | ||
| 1352 | 193, | ||
| 1353 | 45, | ||
| 1354 | 65, | ||
| 1355 | 162, | ||
| 1356 | 214, | ||
| 1357 | 122, | ||
| 1358 | 204, | ||
| 1359 | 60, | ||
| 1360 | 193, | ||
| 1361 | 111, | ||
| 1362 | 208 | ||
| 1363 | ], | ||
| 1243 | "margin_origin": [ | 1364 | "margin_origin": [ |
| 1244 | 0.0, | 1365 | 0.0, |
| 1245 | 0.0 | 1366 | 0.0 |
| ... | @@ -1248,7 +1369,7 @@ | ... | @@ -1248,7 +1369,7 @@ |
| 1248 | { | 1369 | { |
| 1249 | "Title": { | 1370 | "Title": { |
| 1250 | "date": null, | 1371 | "date": null, |
| 1251 | "id": "{57E835EA-2799-4118-B55C-7BB1E51C6941},13", | 1372 | "id": "{31FFA5F9-C1B8-412D-A2D6-7ACC3CC16FD0},13", |
| 1252 | "layout": { | 1373 | "layout": { |
| 1253 | "max_height": null, | 1374 | "max_height": null, |
| 1254 | "max_width": null, | 1375 | "max_width": null, |
| ... | @@ -1259,24 +1380,29 @@ | ... | @@ -1259,24 +1380,29 @@ |
| 1259 | }, | 1380 | }, |
| 1260 | "outlines": [ | 1381 | "outlines": [ |
| 1261 | { | 1382 | { |
| 1262 | "id": "{57E835EA-2799-4118-B55C-7BB1E51C6941},14", | 1383 | "id": "{31FFA5F9-C1B8-412D-A2D6-7ACC3CC16FD0},14", |
| 1263 | "indents": [], | 1384 | "indents": [ |
| 1385 | 18.0, | ||
| 1386 | 0.0, | ||
| 1387 | 27.0, | ||
| 1388 | 27.0 | ||
| 1389 | ], | ||
| 1264 | "layout": { | 1390 | "layout": { |
| 1265 | "max_height": null, | 1391 | "max_height": 21.600000381469727, |
| 1266 | "max_width": null, | 1392 | "max_width": null, |
| 1267 | "reserved_width": null, | 1393 | "reserved_width": null, |
| 1268 | "width_set_by_user": null, | 1394 | "width_set_by_user": null, |
| 1269 | "x": null, | 1395 | "x": null, |
| 1270 | "y": null | 1396 | "y": null |
| 1271 | }, | 1397 | }, |
| 1272 | "min_width": null, | 1398 | "min_width": 162.0, |
| 1273 | "paragraphs": [ | 1399 | "paragraphs": [ |
| 1274 | { | 1400 | { |
| 1275 | "collapsed": false, | 1401 | "collapsed": false, |
| 1276 | "content": { | 1402 | "content": { |
| 1277 | "Text": { | 1403 | "Text": { |
| 1278 | "date_field": null, | 1404 | "date_field": null, |
| 1279 | "id": "{57E835EA-2799-4118-B55C-7BB1E51C6941},16", | 1405 | "id": "{31FFA5F9-C1B8-412D-A2D6-7ACC3CC16FD0},16", |
| 1280 | "tags": [], | 1406 | "tags": [], |
| 1281 | "text": { | 1407 | "text": { |
| 1282 | "spans": [ | 1408 | "spans": [ |
| ... | @@ -1298,6 +1424,7 @@ | ... | @@ -1298,6 +1424,7 @@ |
| 1298 | "line_spacing": null, | 1424 | "line_spacing": null, |
| 1299 | "list_spacing": null, | 1425 | "list_spacing": null, |
| 1300 | "math": null, | 1426 | "math": null, |
| 1427 | "math_object": null, | ||
| 1301 | "rtl": null, | 1428 | "rtl": null, |
| 1302 | "space_after": null, | 1429 | "space_after": null, |
| 1303 | "space_before": null, | 1430 | "space_before": null, |
| ... | @@ -1328,6 +1455,7 @@ | ... | @@ -1328,6 +1455,7 @@ |
| 1328 | "line_spacing": null, | 1455 | "line_spacing": null, |
| 1329 | "list_spacing": null, | 1456 | "list_spacing": null, |
| 1330 | "math": null, | 1457 | "math": null, |
| 1458 | "math_object": null, | ||
| 1331 | "rtl": null, | 1459 | "rtl": null, |
| 1332 | "space_after": null, | 1460 | "space_after": null, |
| 1333 | "space_before": null, | 1461 | "space_before": null, |
| ... | @@ -1336,15 +1464,19 @@ | ... | @@ -1336,15 +1464,19 @@ |
| 1336 | "superscript": null, | 1464 | "superscript": null, |
| 1337 | "underline": null | 1465 | "underline": null |
| 1338 | }, | 1466 | }, |
| 1339 | "id": "{57E835EA-2799-4118-B55C-7BB1E51C6941},15", | 1467 | "id": "{31FFA5F9-C1B8-412D-A2D6-7ACC3CC16FD0},15", |
| 1340 | "level": 1, | 1468 | "level": 1, |
| 1341 | "lists": [], | 1469 | "lists": [], |
| 1470 | "media": { | ||
| 1471 | "recordings": [], | ||
| 1472 | "time_ms": null | ||
| 1473 | }, | ||
| 1342 | "parent": null, | 1474 | "parent": null, |
| 1343 | "style": "{57E835EA-2799-4118-B55C-7BB1E51C6941},18", | 1475 | "style": "{31FFA5F9-C1B8-412D-A2D6-7ACC3CC16FD0},18", |
| 1344 | "tags": [] | 1476 | "tags": [] |
| 1345 | } | 1477 | } |
| 1346 | ], | 1478 | ], |
| 1347 | "title": false, | 1479 | "title": true, |
| 1348 | "unsupported": [] | 1480 | "unsupported": [] |
| 1349 | } | 1481 | } |
| 1350 | ] | 1482 | ] |
| ... | @@ -1355,7 +1487,7 @@ | ... | @@ -1355,7 +1487,7 @@ |
| 1355 | } | 1487 | } |
| 1356 | } | 1488 | } |
| 1357 | }, | 1489 | }, |
| 1358 | "space": "{57E835EA-2799-4118-B55C-7BB1E51C6941},1" | 1490 | "space": "{31FFA5F9-C1B8-412D-A2D6-7ACC3CC16FD0},1" |
| 1359 | } | 1491 | } |
| 1360 | ], | 1492 | ], |
| 1361 | [ | 1493 | [ |
| ... | @@ -1366,215 +1498,215 @@ | ... | @@ -1366,215 +1498,215 @@ |
| 1366 | "edits": [ | 1498 | "edits": [ |
| 1367 | { | 1499 | { |
| 1368 | "guid": [ | 1500 | "guid": [ |
| 1369 | 172, | ||
| 1370 | 90, | ||
| 1371 | 29, | ||
| 1372 | 103, | ||
| 1373 | 245, | ||
| 1374 | 250, | ||
| 1375 | 220, | ||
| 1376 | 74, | ||
| 1377 | 178, | 1501 | 178, |
| 1378 | 30, | 1502 | 65, |
| 1379 | 220, | 1503 | 17, |
| 1380 | 119, | 1504 | 57, |
| 1381 | 124, | 1505 | 15, |
| 1382 | 185, | 1506 | 50, |
| 1383 | 174, | 1507 | 208, |
| 1384 | 198 | 1508 | 79, |
| 1509 | 129, | ||
| 1510 | 89, | ||
| 1511 | 215, | ||
| 1512 | 81, | ||
| 1513 | 45, | ||
| 1514 | 182, | ||
| 1515 | 161, | ||
| 1516 | 182 | ||
| 1385 | ], | 1517 | ], |
| 1386 | "level": 1, | 1518 | "level": 1, |
| 1387 | "position": { | 1519 | "position": { |
| 1388 | "Before": "{361A0F09-EA63-4F2D-9620-C061D4E4707E},1" | 1520 | "Before": "{A94D5882-C902-4CAE-B38E-2ED56EEF177F},1" |
| 1389 | }, | 1521 | }, |
| 1390 | "space": "{EB3940F1-F255-42BE-8C31-DD09E86DBAAF},1" | 1522 | "space": "{5AB37A0F-5702-4C8E-A8C6-79ED6ED1E69F},1" |
| 1391 | }, | 1523 | }, |
| 1392 | { | 1524 | { |
| 1393 | "guid": [ | 1525 | "guid": [ |
| 1394 | 43, | 1526 | 92, |
| 1395 | 65, | 1527 | 129, |
| 1528 | 87, | ||
| 1529 | 179, | ||
| 1530 | 42, | ||
| 1531 | 26, | ||
| 1532 | 81, | ||
| 1533 | 77, | ||
| 1534 | 175, | ||
| 1396 | 11, | 1535 | 11, |
| 1397 | 224, | 1536 | 45, |
| 1398 | 49, | 1537 | 117, |
| 1399 | 201, | 1538 | 57, |
| 1400 | 193, | 1539 | 12, |
| 1401 | 78, | 1540 | 114, |
| 1402 | 183, | 1541 | 224 |
| 1403 | 88, | ||
| 1404 | 16, | ||
| 1405 | 59, | ||
| 1406 | 113, | ||
| 1407 | 75, | ||
| 1408 | 60, | ||
| 1409 | 236 | ||
| 1410 | ], | 1542 | ], |
| 1411 | "level": 2, | 1543 | "level": 2, |
| 1412 | "position": "Keep", | 1544 | "position": "Keep", |
| 1413 | "space": "{C2870D07-8ADC-44BB-9F12-15D6C3708689},1" | 1545 | "space": "{F212795E-0E4B-4844-A8A6-ABD7F7025124},1" |
| 1414 | } | 1546 | } |
| 1415 | ], | 1547 | ], |
| 1416 | "observed": [ | 1548 | "observed": [ |
| 1417 | [ | 1549 | [ |
| 1418 | "{869D5929-6E5E-4EDB-9639-4C56977DD447},1", | 1550 | "{0B37E922-551A-48F8-BCAC-4AB3503B4D28},1", |
| 1419 | 1 | 1551 | 1 |
| 1420 | ], | 1552 | ], |
| 1421 | [ | 1553 | [ |
| 1422 | "{B0B53F94-0105-4C59-B65B-9473F49BFF41},1", | 1554 | "{74492C4A-164B-40E9-B297-4D3B4685347D},1", |
| 1423 | 1 | 1555 | 1 |
| 1424 | ], | 1556 | ], |
| 1425 | [ | 1557 | [ |
| 1426 | "{0E98EE82-B78F-4872-8BD9-5D9B23A817C5},1", | 1558 | "{DAFF533D-608B-4735-BA1D-2354E8CB8A69},1", |
| 1427 | 1 | 1559 | 1 |
| 1428 | ], | 1560 | ], |
| 1429 | [ | 1561 | [ |
| 1430 | "{C837A033-F055-4455-B45F-17D82016921C},1", | 1562 | "{8B766F11-2A4F-4D95-8473-D3CA68129E11},1", |
| 1431 | 1 | 1563 | 1 |
| 1432 | ], | 1564 | ], |
| 1433 | [ | 1565 | [ |
| 1434 | "{89D86121-BF5C-418B-BB92-CBFFA8EF545F},1", | 1566 | "{28E0CC5F-68BC-4A86-9ED7-114FFD3BAE9A},1", |
| 1435 | 1 | 1567 | 1 |
| 1436 | ], | 1568 | ], |
| 1437 | [ | 1569 | [ |
| 1438 | "{57E835EA-2799-4118-B55C-7BB1E51C6941},1", | 1570 | "{31FFA5F9-C1B8-412D-A2D6-7ACC3CC16FD0},1", |
| 1439 | 1 | 1571 | 1 |
| 1440 | ], | 1572 | ], |
| 1441 | [ | 1573 | [ |
| 1442 | "{F7EECCCF-B6B4-44F2-A543-36837EC18B8A},1", | 1574 | "{AFCD09F6-1085-4582-BDB2-4B91E15D2088},1", |
| 1443 | 1 | 1575 | 1 |
| 1444 | ], | 1576 | ], |
| 1445 | [ | 1577 | [ |
| 1446 | "{361A0F09-EA63-4F2D-9620-C061D4E4707E},1", | 1578 | "{A94D5882-C902-4CAE-B38E-2ED56EEF177F},1", |
| 1447 | 1 | 1579 | 1 |
| 1448 | ], | 1580 | ], |
| 1449 | [ | 1581 | [ |
| 1450 | "{EB3940F1-F255-42BE-8C31-DD09E86DBAAF},1", | 1582 | "{5AB37A0F-5702-4C8E-A8C6-79ED6ED1E69F},1", |
| 1451 | 1 | 1583 | 1 |
| 1452 | ], | 1584 | ], |
| 1453 | [ | 1585 | [ |
| 1454 | "{C2870D07-8ADC-44BB-9F12-15D6C3708689},1", | 1586 | "{F212795E-0E4B-4844-A8A6-ABD7F7025124},1", |
| 1455 | 1 | 1587 | 1 |
| 1456 | ], | 1588 | ], |
| 1457 | [ | 1589 | [ |
| 1458 | "{1E0C6331-32C9-4EF9-8087-150A8669FA91},1", | 1590 | "{1786F2CA-0A43-4081-8B55-B207EEE0331C},1", |
| 1459 | 1 | 1591 | 1 |
| 1460 | ], | 1592 | ], |
| 1461 | [ | 1593 | [ |
| 1462 | "{ED9769DD-99D6-4BE2-B980-580DA9C4DFCE},1", | 1594 | "{3E966EF8-B7B4-483F-B241-8F21DF318AB3},1", |
| 1463 | 1 | 1595 | 1 |
| 1464 | ], | 1596 | ], |
| 1465 | [ | 1597 | [ |
| 1466 | "{17C30054-7129-4A10-9D00-4D5545E4F4BA},1", | 1598 | "{0D361C76-92FD-4B76-901B-809C2C3C3CE6},1", |
| 1467 | 1 | 1599 | 1 |
| 1468 | ], | 1600 | ], |
| 1469 | [ | 1601 | [ |
| 1470 | "{4C5FBA5B-E348-4750-ACE4-E9694D527BFF},1", | 1602 | "{AE89305A-73F4-46B5-B41D-18884685D896},1", |
| 1471 | 1 | 1603 | 1 |
| 1472 | ], | 1604 | ], |
| 1473 | [ | 1605 | [ |
| 1474 | "{BD0EF658-4D0F-42A7-B546-B4F4A77D246B},1", | 1606 | "{551D2637-2AA7-4923-B2DD-31C2E1489FD2},1", |
| 1475 | 1 | 1607 | 1 |
| 1476 | ], | 1608 | ], |
| 1477 | [ | 1609 | [ |
| 1478 | "{6FEA70FA-1451-4A27-8A8E-C4CC3B713CF6},1", | 1610 | "{CBF82C86-3A8B-457F-92A4-1AC7BF436CE9},1", |
| 1479 | 1 | 1611 | 1 |
| 1480 | ], | 1612 | ], |
| 1481 | [ | 1613 | [ |
| 1482 | "{68A1E0E1-7A39-4AB6-AD71-4D8A18878D79},1", | 1614 | "{42010094-E377-4A97-9395-AD45808D0552},1", |
| 1483 | 1 | 1615 | 1 |
| 1484 | ], | 1616 | ], |
| 1485 | [ | 1617 | [ |
| 1486 | "{7E9041BD-301B-4B5B-9456-1C92050E4914},1", | 1618 | "{0F6ABB5E-A021-4E1D-B187-7A72E74000DC},1", |
| 1487 | 1 | 1619 | 1 |
| 1488 | ], | 1620 | ], |
| 1489 | [ | 1621 | [ |
| 1490 | "{EFA77BAE-8A98-4176-B536-B615D1D2ED2B},1", | 1622 | "{F9DA3139-5856-4B11-95C0-356683E3ACFE},1", |
| 1491 | 1 | 1623 | 1 |
| 1492 | ], | 1624 | ], |
| 1493 | [ | 1625 | [ |
| 1494 | "{87AC0239-51A5-47C6-BAF4-926CCF56385C},1", | 1626 | "{439B80E0-9D4C-4A2F-9163-4E301491364D},1", |
| 1495 | 1 | 1627 | 1 |
| 1496 | ], | 1628 | ], |
| 1497 | [ | 1629 | [ |
| 1498 | "{C50BF7FC-F848-4B91-B8C8-743D0AA5F195},1", | 1630 | "{B03B973C-50BC-4B9A-9914-B1D5C0CF6197},1", |
| 1499 | 1 | 1631 | 1 |
| 1500 | ], | 1632 | ], |
| 1501 | [ | 1633 | [ |
| 1502 | "{FD8D07D0-C447-4516-A7DB-F8522B77D4A7},1", | 1634 | "{A459C474-E6EB-4BE5-9193-271799B2860B},1", |
| 1503 | 1 | 1635 | 1 |
| 1504 | ], | 1636 | ], |
| 1505 | [ | 1637 | [ |
| 1506 | "{D2E1FE57-F45D-49C3-B388-95222ACA3886},1", | 1638 | "{4ED14CEE-8560-4125-B488-9945C116741E},1", |
| 1507 | 1 | 1639 | 1 |
| 1508 | ], | 1640 | ], |
| 1509 | [ | 1641 | [ |
| 1510 | "{7F6702B3-C977-436F-82AF-403E2BE950E7},1", | 1642 | "{C4FF8FE4-4C43-461F-98DD-EB1FF2A863B2},1", |
| 1511 | 1 | 1643 | 1 |
| 1512 | ], | 1644 | ], |
| 1513 | [ | 1645 | [ |
| 1514 | "{688C4EA1-80F5-4D85-B9BB-F8633FF3C0CA},1", | 1646 | "{932BE37F-05AA-4EFB-85D8-A33445E738B3},1", |
| 1515 | 1 | 1647 | 1 |
| 1516 | ], | 1648 | ], |
| 1517 | [ | 1649 | [ |
| 1518 | "{754FA957-C432-4502-A5CF-EE44449462C0},1", | 1650 | "{9B755368-1D49-4D7E-8D33-6DC536F57848},1", |
| 1519 | 1 | 1651 | 1 |
| 1520 | ], | 1652 | ], |
| 1521 | [ | 1653 | [ |
| 1522 | "{D2197C55-D962-4DF0-8B14-26F88C06D6FA},1", | 1654 | "{D045EAAF-ACE9-4B4F-979B-E348A4ADE693},1", |
| 1523 | 1 | 1655 | 1 |
| 1524 | ], | 1656 | ], |
| 1525 | [ | 1657 | [ |
| 1526 | "{87B83BA8-A906-4F71-A6CA-3E8133A75D34},1", | 1658 | "{5CA4C59F-B400-4BC6-8B90-30ECAC35CB43},1", |
| 1527 | 1 | 1659 | 1 |
| 1528 | ], | 1660 | ], |
| 1529 | [ | 1661 | [ |
| 1530 | "{F2D6AEBB-98EA-4F10-991F-5243E74AF450},1", | 1662 | "{D63FB383-FB37-44F9-AEBB-E868E903A2BB},1", |
| 1531 | 1 | 1663 | 1 |
| 1532 | ], | 1664 | ], |
| 1533 | [ | 1665 | [ |
| 1534 | "{406687D7-F9E9-491B-85DA-166DE6CD5A60},1", | 1666 | "{C6E54364-6CD0-4764-8180-873030B9EA53},1", |
| 1535 | 1 | 1667 | 1 |
| 1536 | ], | 1668 | ], |
| 1537 | [ | 1669 | [ |
| 1538 | "{1F73BF36-9FC2-495D-844D-B370D5C47DE7},1", | 1670 | "{DEEC391B-F2D1-4EB1-BEB1-57598DFBB982},1", |
| 1539 | 1 | 1671 | 1 |
| 1540 | ], | 1672 | ], |
| 1541 | [ | 1673 | [ |
| 1542 | "{E9BD343F-4A66-4DE2-81F9-3789CCA22818},1", | 1674 | "{9E7616ED-DE19-4625-8EA0-0C5DEB18B690},1", |
| 1543 | 1 | 1675 | 1 |
| 1544 | ], | 1676 | ], |
| 1545 | [ | 1677 | [ |
| 1546 | "{189EA744-BE13-467A-95BA-2667A134C0B0},1", | 1678 | "{89F1748D-6749-4AD0-BAF6-07E9F9C058E5},1", |
| 1547 | 1 | 1679 | 1 |
| 1548 | ], | 1680 | ], |
| 1549 | [ | 1681 | [ |
| 1550 | "{017F5881-F818-4C7C-B9F9-6BCB4085F1D0},1", | 1682 | "{00B10C58-6649-428D-A081-3EBBA6E88E97},1", |
| 1551 | 1 | 1683 | 1 |
| 1552 | ], | 1684 | ], |
| 1553 | [ | 1685 | [ |
| 1554 | "{CD0D17F4-25D8-4076-9AF7-47D9CDEFCF0A},1", | 1686 | "{58E99482-710A-4938-8CFB-88A1C9B946C9},1", |
| 1555 | 1 | 1687 | 1 |
| 1556 | ], | 1688 | ], |
| 1557 | [ | 1689 | [ |
| 1558 | "{CCBB5969-BCE8-498B-BE1A-98FD3A44DB1A},1", | 1690 | "{F721C9D5-9A6A-405F-94C9-9B85A7EEB980},1", |
| 1559 | 1 | 1691 | 1 |
| 1560 | ], | 1692 | ], |
| 1561 | [ | 1693 | [ |
| 1562 | "{2B736B19-2D9C-4E76-A732-478D533D717A},1", | 1694 | "{15A45C01-A9E8-42E2-A267-C7FCE728815E},1", |
| 1563 | 1 | 1695 | 1 |
| 1564 | ] | 1696 | ] |
| 1565 | ] | 1697 | ] |
| 1566 | } | 1698 | } |
| 1567 | }, | 1699 | }, |
| 1568 | "space": "{90FCDB0C-36AE-422F-B4FE-67676826FE36},1" | 1700 | "space": "{A5A2AFBE-1DF1-4190-AAC7-1AD29C9564F2},1" |
| 1569 | }, | 1701 | }, |
| 1570 | { | 1702 | { |
| 1571 | "id": 2, | 1703 | "id": 2, |
| 1572 | "operation": { | 1704 | "operation": { |
| 1573 | "Page": { | 1705 | "Page": { |
| 1574 | "after": { | 1706 | "after": { |
| 1575 | "created": 134335976560000000, | 1707 | "created": 134348779890000000, |
| 1576 | "definitions": { | 1708 | "definitions": { |
| 1577 | "{EB3940F1-F255-42BE-8C31-DD09E86DBAAF},18": { | 1709 | "{5AB37A0F-5702-4C8E-A8C6-79ED6ED1E69F},18": { |
| 1578 | "format": { | 1710 | "format": { |
| 1579 | "alignment": null, | 1711 | "alignment": null, |
| 1580 | "bold": null, | 1712 | "bold": null, |
| ... | @@ -1591,6 +1723,7 @@ | ... | @@ -1591,6 +1723,7 @@ |
| 1591 | "line_spacing": null, | 1723 | "line_spacing": null, |
| 1592 | "list_spacing": null, | 1724 | "list_spacing": null, |
| 1593 | "math": null, | 1725 | "math": null, |
| 1726 | "math_object": null, | ||
| 1594 | "rtl": null, | 1727 | "rtl": null, |
| 1595 | "space_after": null, | 1728 | "space_after": null, |
| 1596 | "space_before": null, | 1729 | "space_before": null, |
| ... | @@ -1605,6 +1738,24 @@ | ... | @@ -1605,6 +1738,24 @@ |
| 1605 | } | 1738 | } |
| 1606 | } | 1739 | } |
| 1607 | }, | 1740 | }, |
| 1741 | "identity": [ | ||
| 1742 | 15, | ||
| 1743 | 122, | ||
| 1744 | 179, | ||
| 1745 | 90, | ||
| 1746 | 2, | ||
| 1747 | 87, | ||
| 1748 | 142, | ||
| 1749 | 76, | ||
| 1750 | 168, | ||
| 1751 | 198, | ||
| 1752 | 121, | ||
| 1753 | 237, | ||
| 1754 | 110, | ||
| 1755 | 209, | ||
| 1756 | 230, | ||
| 1757 | 159 | ||
| 1758 | ], | ||
| 1608 | "margin_origin": [ | 1759 | "margin_origin": [ |
| 1609 | 0.0, | 1760 | 0.0, |
| 1610 | 0.0 | 1761 | 0.0 |
| ... | @@ -1612,7 +1763,7 @@ | ... | @@ -1612,7 +1763,7 @@ |
| 1612 | "objects": [ | 1763 | "objects": [ |
| 1613 | { | 1764 | { |
| 1614 | "Outline": { | 1765 | "Outline": { |
| 1615 | "id": "{5C473AF1-9E02-4E9A-BFC8-9E9CBBB289E1},0", | 1766 | "id": "{91379CE6-91DA-48BA-B306-77E4CEB068B6},8", |
| 1616 | "indents": [], | 1767 | "indents": [], |
| 1617 | "layout": { | 1768 | "layout": { |
| 1618 | "max_height": null, | 1769 | "max_height": null, |
| ... | @@ -1629,7 +1780,7 @@ | ... | @@ -1629,7 +1780,7 @@ |
| 1629 | "content": { | 1780 | "content": { |
| 1630 | "Text": { | 1781 | "Text": { |
| 1631 | "date_field": null, | 1782 | "date_field": null, |
| 1632 | "id": "{B412B53F-86FF-4462-8750-FB4D73532A63},0", | 1783 | "id": "{91379CE6-91DA-48BA-B306-77E4CEB068B6},7", |
| 1633 | "tags": [], | 1784 | "tags": [], |
| 1634 | "text": { | 1785 | "text": { |
| 1635 | "spans": [ | 1786 | "spans": [ |
| ... | @@ -1651,6 +1802,7 @@ | ... | @@ -1651,6 +1802,7 @@ |
| 1651 | "line_spacing": null, | 1802 | "line_spacing": null, |
| 1652 | "list_spacing": null, | 1803 | "list_spacing": null, |
| 1653 | "math": null, | 1804 | "math": null, |
| 1805 | "math_object": null, | ||
| 1654 | "rtl": null, | 1806 | "rtl": null, |
| 1655 | "space_after": null, | 1807 | "space_after": null, |
| 1656 | "space_before": null, | 1808 | "space_before": null, |
| ... | @@ -1681,6 +1833,7 @@ | ... | @@ -1681,6 +1833,7 @@ |
| 1681 | "line_spacing": null, | 1833 | "line_spacing": null, |
| 1682 | "list_spacing": null, | 1834 | "list_spacing": null, |
| 1683 | "math": null, | 1835 | "math": null, |
| 1836 | "math_object": null, | ||
| 1684 | "rtl": null, | 1837 | "rtl": null, |
| 1685 | "space_after": null, | 1838 | "space_after": null, |
| 1686 | "space_before": null, | 1839 | "space_before": null, |
| ... | @@ -1689,9 +1842,13 @@ | ... | @@ -1689,9 +1842,13 @@ |
| 1689 | "superscript": null, | 1842 | "superscript": null, |
| 1690 | "underline": null | 1843 | "underline": null |
| 1691 | }, | 1844 | }, |
| 1692 | "id": "{ADE09A18-DF3F-4995-BF31-D453831561D7},0", | 1845 | "id": "{91379CE6-91DA-48BA-B306-77E4CEB068B6},9", |
| 1693 | "level": 1, | 1846 | "level": 1, |
| 1694 | "lists": [], | 1847 | "lists": [], |
| 1848 | "media": { | ||
| 1849 | "recordings": [], | ||
| 1850 | "time_ms": null | ||
| 1851 | }, | ||
| 1695 | "parent": null, | 1852 | "parent": null, |
| 1696 | "style": null, | 1853 | "style": null, |
| 1697 | "tags": [] | 1854 | "tags": [] |
| ... | @@ -1704,7 +1861,7 @@ | ... | @@ -1704,7 +1861,7 @@ |
| 1704 | { | 1861 | { |
| 1705 | "Title": { | 1862 | "Title": { |
| 1706 | "date": null, | 1863 | "date": null, |
| 1707 | "id": "{EB3940F1-F255-42BE-8C31-DD09E86DBAAF},13", | 1864 | "id": "{5AB37A0F-5702-4C8E-A8C6-79ED6ED1E69F},13", |
| 1708 | "layout": { | 1865 | "layout": { |
| 1709 | "max_height": null, | 1866 | "max_height": null, |
| 1710 | "max_width": null, | 1867 | "max_width": null, |
| ... | @@ -1715,24 +1872,29 @@ | ... | @@ -1715,24 +1872,29 @@ |
| 1715 | }, | 1872 | }, |
| 1716 | "outlines": [ | 1873 | "outlines": [ |
| 1717 | { | 1874 | { |
| 1718 | "id": "{EB3940F1-F255-42BE-8C31-DD09E86DBAAF},14", | 1875 | "id": "{5AB37A0F-5702-4C8E-A8C6-79ED6ED1E69F},14", |
| 1719 | "indents": [], | 1876 | "indents": [ |
| 1877 | 18.0, | ||
| 1878 | 0.0, | ||
| 1879 | 27.0, | ||
| 1880 | 27.0 | ||
| 1881 | ], | ||
| 1720 | "layout": { | 1882 | "layout": { |
| 1721 | "max_height": null, | 1883 | "max_height": 21.600000381469727, |
| 1722 | "max_width": null, | 1884 | "max_width": null, |
| 1723 | "reserved_width": null, | 1885 | "reserved_width": null, |
| 1724 | "width_set_by_user": null, | 1886 | "width_set_by_user": null, |
| 1725 | "x": null, | 1887 | "x": null, |
| 1726 | "y": null | 1888 | "y": null |
| 1727 | }, | 1889 | }, |
| 1728 | "min_width": null, | 1890 | "min_width": 162.0, |
| 1729 | "paragraphs": [ | 1891 | "paragraphs": [ |
| 1730 | { | 1892 | { |
| 1731 | "collapsed": false, | 1893 | "collapsed": false, |
| 1732 | "content": { | 1894 | "content": { |
| 1733 | "Text": { | 1895 | "Text": { |
| 1734 | "date_field": null, | 1896 | "date_field": null, |
| 1735 | "id": "{EB3940F1-F255-42BE-8C31-DD09E86DBAAF},16", | 1897 | "id": "{5AB37A0F-5702-4C8E-A8C6-79ED6ED1E69F},16", |
| 1736 | "tags": [], | 1898 | "tags": [], |
| 1737 | "text": { | 1899 | "text": { |
| 1738 | "spans": [ | 1900 | "spans": [ |
| ... | @@ -1754,6 +1916,7 @@ | ... | @@ -1754,6 +1916,7 @@ |
| 1754 | "line_spacing": null, | 1916 | "line_spacing": null, |
| 1755 | "list_spacing": null, | 1917 | "list_spacing": null, |
| 1756 | "math": null, | 1918 | "math": null, |
| 1919 | "math_object": null, | ||
| 1757 | "rtl": null, | 1920 | "rtl": null, |
| 1758 | "space_after": null, | 1921 | "space_after": null, |
| 1759 | "space_before": null, | 1922 | "space_before": null, |
| ... | @@ -1784,6 +1947,7 @@ | ... | @@ -1784,6 +1947,7 @@ |
| 1784 | "line_spacing": null, | 1947 | "line_spacing": null, |
| 1785 | "list_spacing": null, | 1948 | "list_spacing": null, |
| 1786 | "math": null, | 1949 | "math": null, |
| 1950 | "math_object": null, | ||
| 1787 | "rtl": null, | 1951 | "rtl": null, |
| 1788 | "space_after": null, | 1952 | "space_after": null, |
| 1789 | "space_before": null, | 1953 | "space_before": null, |
| ... | @@ -1792,15 +1956,19 @@ | ... | @@ -1792,15 +1956,19 @@ |
| 1792 | "superscript": null, | 1956 | "superscript": null, |
| 1793 | "underline": null | 1957 | "underline": null |
| 1794 | }, | 1958 | }, |
| 1795 | "id": "{EB3940F1-F255-42BE-8C31-DD09E86DBAAF},15", | 1959 | "id": "{5AB37A0F-5702-4C8E-A8C6-79ED6ED1E69F},15", |
| 1796 | "level": 1, | 1960 | "level": 1, |
| 1797 | "lists": [], | 1961 | "lists": [], |
| 1962 | "media": { | ||
| 1963 | "recordings": [], | ||
| 1964 | "time_ms": null | ||
| 1965 | }, | ||
| 1798 | "parent": null, | 1966 | "parent": null, |
| 1799 | "style": "{EB3940F1-F255-42BE-8C31-DD09E86DBAAF},18", | 1967 | "style": "{5AB37A0F-5702-4C8E-A8C6-79ED6ED1E69F},18", |
| 1800 | "tags": [] | 1968 | "tags": [] |
| 1801 | } | 1969 | } |
| 1802 | ], | 1970 | ], |
| 1803 | "title": false, | 1971 | "title": true, |
| 1804 | "unsupported": [] | 1972 | "unsupported": [] |
| 1805 | } | 1973 | } |
| 1806 | ] | 1974 | ] |
| ... | @@ -1811,9 +1979,9 @@ | ... | @@ -1811,9 +1979,9 @@ |
| 1811 | }, | 1979 | }, |
| 1812 | "author": "Author", | 1980 | "author": "Author", |
| 1813 | "before": { | 1981 | "before": { |
| 1814 | "created": 134335976560000000, | 1982 | "created": 134348779890000000, |
| 1815 | "definitions": { | 1983 | "definitions": { |
| 1816 | "{EB3940F1-F255-42BE-8C31-DD09E86DBAAF},18": { | 1984 | "{5AB37A0F-5702-4C8E-A8C6-79ED6ED1E69F},18": { |
| 1817 | "format": { | 1985 | "format": { |
| 1818 | "alignment": null, | 1986 | "alignment": null, |
| 1819 | "bold": null, | 1987 | "bold": null, |
| ... | @@ -1830,6 +1998,7 @@ | ... | @@ -1830,6 +1998,7 @@ |
| 1830 | "line_spacing": null, | 1998 | "line_spacing": null, |
| 1831 | "list_spacing": null, | 1999 | "list_spacing": null, |
| 1832 | "math": null, | 2000 | "math": null, |
| 2001 | "math_object": null, | ||
| 1833 | "rtl": null, | 2002 | "rtl": null, |
| 1834 | "space_after": null, | 2003 | "space_after": null, |
| 1835 | "space_before": null, | 2004 | "space_before": null, |
| ... | @@ -1844,6 +2013,24 @@ | ... | @@ -1844,6 +2013,24 @@ |
| 1844 | } | 2013 | } |
| 1845 | } | 2014 | } |
| 1846 | }, | 2015 | }, |
| 2016 | "identity": [ | ||
| 2017 | 15, | ||
| 2018 | 122, | ||
| 2019 | 179, | ||
| 2020 | 90, | ||
| 2021 | 2, | ||
| 2022 | 87, | ||
| 2023 | 142, | ||
| 2024 | 76, | ||
| 2025 | 168, | ||
| 2026 | 198, | ||
| 2027 | 121, | ||
| 2028 | 237, | ||
| 2029 | 110, | ||
| 2030 | 209, | ||
| 2031 | 230, | ||
| 2032 | 159 | ||
| 2033 | ], | ||
| 1847 | "margin_origin": [ | 2034 | "margin_origin": [ |
| 1848 | 0.0, | 2035 | 0.0, |
| 1849 | 0.0 | 2036 | 0.0 |
| ... | @@ -1852,7 +2039,7 @@ | ... | @@ -1852,7 +2039,7 @@ |
| 1852 | { | 2039 | { |
| 1853 | "Title": { | 2040 | "Title": { |
| 1854 | "date": null, | 2041 | "date": null, |
| 1855 | "id": "{EB3940F1-F255-42BE-8C31-DD09E86DBAAF},13", | 2042 | "id": "{5AB37A0F-5702-4C8E-A8C6-79ED6ED1E69F},13", |
| 1856 | "layout": { | 2043 | "layout": { |
| 1857 | "max_height": null, | 2044 | "max_height": null, |
| 1858 | "max_width": null, | 2045 | "max_width": null, |
| ... | @@ -1863,24 +2050,29 @@ | ... | @@ -1863,24 +2050,29 @@ |
| 1863 | }, | 2050 | }, |
| 1864 | "outlines": [ | 2051 | "outlines": [ |
| 1865 | { | 2052 | { |
| 1866 | "id": "{EB3940F1-F255-42BE-8C31-DD09E86DBAAF},14", | 2053 | "id": "{5AB37A0F-5702-4C8E-A8C6-79ED6ED1E69F},14", |
| 1867 | "indents": [], | 2054 | "indents": [ |
| 2055 | 18.0, | ||
| 2056 | 0.0, | ||
| 2057 | 27.0, | ||
| 2058 | 27.0 | ||
| 2059 | ], | ||
| 1868 | "layout": { | 2060 | "layout": { |
| 1869 | "max_height": null, | 2061 | "max_height": 21.600000381469727, |
| 1870 | "max_width": null, | 2062 | "max_width": null, |
| 1871 | "reserved_width": null, | 2063 | "reserved_width": null, |
| 1872 | "width_set_by_user": null, | 2064 | "width_set_by_user": null, |
| 1873 | "x": null, | 2065 | "x": null, |
| 1874 | "y": null | 2066 | "y": null |
| 1875 | }, | 2067 | }, |
| 1876 | "min_width": null, | 2068 | "min_width": 162.0, |
| 1877 | "paragraphs": [ | 2069 | "paragraphs": [ |
| 1878 | { | 2070 | { |
| 1879 | "collapsed": false, | 2071 | "collapsed": false, |
| 1880 | "content": { | 2072 | "content": { |
| 1881 | "Text": { | 2073 | "Text": { |
| 1882 | "date_field": null, | 2074 | "date_field": null, |
| 1883 | "id": "{EB3940F1-F255-42BE-8C31-DD09E86DBAAF},16", | 2075 | "id": "{5AB37A0F-5702-4C8E-A8C6-79ED6ED1E69F},16", |
| 1884 | "tags": [], | 2076 | "tags": [], |
| 1885 | "text": { | 2077 | "text": { |
| 1886 | "spans": [ | 2078 | "spans": [ |
| ... | @@ -1902,6 +2094,7 @@ | ... | @@ -1902,6 +2094,7 @@ |
| 1902 | "line_spacing": null, | 2094 | "line_spacing": null, |
| 1903 | "list_spacing": null, | 2095 | "list_spacing": null, |
| 1904 | "math": null, | 2096 | "math": null, |
| 2097 | "math_object": null, | ||
| 1905 | "rtl": null, | 2098 | "rtl": null, |
| 1906 | "space_after": null, | 2099 | "space_after": null, |
| 1907 | "space_before": null, | 2100 | "space_before": null, |
| ... | @@ -1932,6 +2125,7 @@ | ... | @@ -1932,6 +2125,7 @@ |
| 1932 | "line_spacing": null, | 2125 | "line_spacing": null, |
| 1933 | "list_spacing": null, | 2126 | "list_spacing": null, |
| 1934 | "math": null, | 2127 | "math": null, |
| 2128 | "math_object": null, | ||
| 1935 | "rtl": null, | 2129 | "rtl": null, |
| 1936 | "space_after": null, | 2130 | "space_after": null, |
| 1937 | "space_before": null, | 2131 | "space_before": null, |
| ... | @@ -1940,15 +2134,19 @@ | ... | @@ -1940,15 +2134,19 @@ |
| 1940 | "superscript": null, | 2134 | "superscript": null, |
| 1941 | "underline": null | 2135 | "underline": null |
| 1942 | }, | 2136 | }, |
| 1943 | "id": "{EB3940F1-F255-42BE-8C31-DD09E86DBAAF},15", | 2137 | "id": "{5AB37A0F-5702-4C8E-A8C6-79ED6ED1E69F},15", |
| 1944 | "level": 1, | 2138 | "level": 1, |
| 1945 | "lists": [], | 2139 | "lists": [], |
| 2140 | "media": { | ||
| 2141 | "recordings": [], | ||
| 2142 | "time_ms": null | ||
| 2143 | }, | ||
| 1946 | "parent": null, | 2144 | "parent": null, |
| 1947 | "style": "{EB3940F1-F255-42BE-8C31-DD09E86DBAAF},18", | 2145 | "style": "{5AB37A0F-5702-4C8E-A8C6-79ED6ED1E69F},18", |
| 1948 | "tags": [] | 2146 | "tags": [] |
| 1949 | } | 2147 | } |
| 1950 | ], | 2148 | ], |
| 1951 | "title": false, | 2149 | "title": true, |
| 1952 | "unsupported": [] | 2150 | "unsupported": [] |
| 1953 | } | 2151 | } |
| 1954 | ] | 2152 | ] |
| ... | @@ -1959,7 +2157,7 @@ | ... | @@ -1959,7 +2157,7 @@ |
| 1959 | } | 2157 | } |
| 1960 | } | 2158 | } |
| 1961 | }, | 2159 | }, |
| 1962 | "space": "{EB3940F1-F255-42BE-8C31-DD09E86DBAAF},1" | 2160 | "space": "{5AB37A0F-5702-4C8E-A8C6-79ED6ED1E69F},1" |
| 1963 | } | 2161 | } |
| 1964 | ], | 2162 | ], |
| 1965 | [ | 2163 | [ |
| ... | @@ -1970,215 +2168,215 @@ | ... | @@ -1970,215 +2168,215 @@ |
| 1970 | "edits": [ | 2168 | "edits": [ |
| 1971 | { | 2169 | { |
| 1972 | "guid": [ | 2170 | "guid": [ |
| 1973 | 220, | 2171 | 202, |
| 1974 | 22, | 2172 | 70, |
| 2173 | 93, | ||
| 2174 | 136, | ||
| 2175 | 36, | ||
| 2176 | 165, | ||
| 1975 | 109, | 2177 | 109, |
| 1976 | 148, | 2178 | 65, |
| 1977 | 199, | 2179 | 139, |
| 1978 | 94, | 2180 | 122, |
| 1979 | 248, | 2181 | 213, |
| 1980 | 76, | 2182 | 126, |
| 1981 | 168, | 2183 | 228, |
| 1982 | 183, | 2184 | 97, |
| 1983 | 186, | 2185 | 103, |
| 1984 | 119, | 2186 | 255 |
| 1985 | 172, | ||
| 1986 | 76, | ||
| 1987 | 100, | ||
| 1988 | 21 | ||
| 1989 | ], | 2187 | ], |
| 1990 | "level": 1, | 2188 | "level": 1, |
| 1991 | "position": { | 2189 | "position": { |
| 1992 | "Before": "{1E0C6331-32C9-4EF9-8087-150A8669FA91},1" | 2190 | "Before": "{1786F2CA-0A43-4081-8B55-B207EEE0331C},1" |
| 1993 | }, | 2191 | }, |
| 1994 | "space": "{ED9769DD-99D6-4BE2-B980-580DA9C4DFCE},1" | 2192 | "space": "{3E966EF8-B7B4-483F-B241-8F21DF318AB3},1" |
| 1995 | }, | 2193 | }, |
| 1996 | { | 2194 | { |
| 1997 | "guid": [ | 2195 | "guid": [ |
| 1998 | 177, | 2196 | 233, |
| 1999 | 33, | 2197 | 86, |
| 2000 | 19, | 2198 | 59, |
| 2001 | 250, | 2199 | 11, |
| 2002 | 24, | 2200 | 202, |
| 2003 | 200, | 2201 | 9, |
| 2004 | 209, | 2202 | 167, |
| 2005 | 74, | 2203 | 68, |
| 2006 | 175, | 2204 | 172, |
| 2007 | 22, | 2205 | 213, |
| 2008 | 210, | 2206 | 140, |
| 2009 | 214, | 2207 | 32, |
| 2010 | 251, | 2208 | 40, |
| 2011 | 44, | 2209 | 205, |
| 2012 | 125, | 2210 | 125, |
| 2013 | 123 | 2211 | 172 |
| 2014 | ], | 2212 | ], |
| 2015 | "level": 2, | 2213 | "level": 2, |
| 2016 | "position": "Keep", | 2214 | "position": "Keep", |
| 2017 | "space": "{17C30054-7129-4A10-9D00-4D5545E4F4BA},1" | 2215 | "space": "{0D361C76-92FD-4B76-901B-809C2C3C3CE6},1" |
| 2018 | } | 2216 | } |
| 2019 | ], | 2217 | ], |
| 2020 | "observed": [ | 2218 | "observed": [ |
| 2021 | [ | 2219 | [ |
| 2022 | "{869D5929-6E5E-4EDB-9639-4C56977DD447},1", | 2220 | "{0B37E922-551A-48F8-BCAC-4AB3503B4D28},1", |
| 2023 | 1 | 2221 | 1 |
| 2024 | ], | 2222 | ], |
| 2025 | [ | 2223 | [ |
| 2026 | "{B0B53F94-0105-4C59-B65B-9473F49BFF41},1", | 2224 | "{74492C4A-164B-40E9-B297-4D3B4685347D},1", |
| 2027 | 1 | 2225 | 1 |
| 2028 | ], | 2226 | ], |
| 2029 | [ | 2227 | [ |
| 2030 | "{0E98EE82-B78F-4872-8BD9-5D9B23A817C5},1", | 2228 | "{DAFF533D-608B-4735-BA1D-2354E8CB8A69},1", |
| 2031 | 1 | 2229 | 1 |
| 2032 | ], | 2230 | ], |
| 2033 | [ | 2231 | [ |
| 2034 | "{C837A033-F055-4455-B45F-17D82016921C},1", | 2232 | "{8B766F11-2A4F-4D95-8473-D3CA68129E11},1", |
| 2035 | 1 | 2233 | 1 |
| 2036 | ], | 2234 | ], |
| 2037 | [ | 2235 | [ |
| 2038 | "{89D86121-BF5C-418B-BB92-CBFFA8EF545F},1", | 2236 | "{28E0CC5F-68BC-4A86-9ED7-114FFD3BAE9A},1", |
| 2039 | 1 | 2237 | 1 |
| 2040 | ], | 2238 | ], |
| 2041 | [ | 2239 | [ |
| 2042 | "{57E835EA-2799-4118-B55C-7BB1E51C6941},1", | 2240 | "{31FFA5F9-C1B8-412D-A2D6-7ACC3CC16FD0},1", |
| 2043 | 1 | 2241 | 1 |
| 2044 | ], | 2242 | ], |
| 2045 | [ | 2243 | [ |
| 2046 | "{F7EECCCF-B6B4-44F2-A543-36837EC18B8A},1", | 2244 | "{AFCD09F6-1085-4582-BDB2-4B91E15D2088},1", |
| 2047 | 1 | 2245 | 1 |
| 2048 | ], | 2246 | ], |
| 2049 | [ | 2247 | [ |
| 2050 | "{361A0F09-EA63-4F2D-9620-C061D4E4707E},1", | 2248 | "{A94D5882-C902-4CAE-B38E-2ED56EEF177F},1", |
| 2051 | 1 | 2249 | 1 |
| 2052 | ], | 2250 | ], |
| 2053 | [ | 2251 | [ |
| 2054 | "{EB3940F1-F255-42BE-8C31-DD09E86DBAAF},1", | 2252 | "{5AB37A0F-5702-4C8E-A8C6-79ED6ED1E69F},1", |
| 2055 | 1 | 2253 | 1 |
| 2056 | ], | 2254 | ], |
| 2057 | [ | 2255 | [ |
| 2058 | "{C2870D07-8ADC-44BB-9F12-15D6C3708689},1", | 2256 | "{F212795E-0E4B-4844-A8A6-ABD7F7025124},1", |
| 2059 | 1 | 2257 | 1 |
| 2060 | ], | 2258 | ], |
| 2061 | [ | 2259 | [ |
| 2062 | "{1E0C6331-32C9-4EF9-8087-150A8669FA91},1", | 2260 | "{1786F2CA-0A43-4081-8B55-B207EEE0331C},1", |
| 2063 | 1 | 2261 | 1 |
| 2064 | ], | 2262 | ], |
| 2065 | [ | 2263 | [ |
| 2066 | "{ED9769DD-99D6-4BE2-B980-580DA9C4DFCE},1", | 2264 | "{3E966EF8-B7B4-483F-B241-8F21DF318AB3},1", |
| 2067 | 1 | 2265 | 1 |
| 2068 | ], | 2266 | ], |
| 2069 | [ | 2267 | [ |
| 2070 | "{17C30054-7129-4A10-9D00-4D5545E4F4BA},1", | 2268 | "{0D361C76-92FD-4B76-901B-809C2C3C3CE6},1", |
| 2071 | 1 | 2269 | 1 |
| 2072 | ], | 2270 | ], |
| 2073 | [ | 2271 | [ |
| 2074 | "{4C5FBA5B-E348-4750-ACE4-E9694D527BFF},1", | 2272 | "{AE89305A-73F4-46B5-B41D-18884685D896},1", |
| 2075 | 1 | 2273 | 1 |
| 2076 | ], | 2274 | ], |
| 2077 | [ | 2275 | [ |
| 2078 | "{BD0EF658-4D0F-42A7-B546-B4F4A77D246B},1", | 2276 | "{551D2637-2AA7-4923-B2DD-31C2E1489FD2},1", |
| 2079 | 1 | 2277 | 1 |
| 2080 | ], | 2278 | ], |
| 2081 | [ | 2279 | [ |
| 2082 | "{6FEA70FA-1451-4A27-8A8E-C4CC3B713CF6},1", | 2280 | "{CBF82C86-3A8B-457F-92A4-1AC7BF436CE9},1", |
| 2083 | 1 | 2281 | 1 |
| 2084 | ], | 2282 | ], |
| 2085 | [ | 2283 | [ |
| 2086 | "{68A1E0E1-7A39-4AB6-AD71-4D8A18878D79},1", | 2284 | "{42010094-E377-4A97-9395-AD45808D0552},1", |
| 2087 | 1 | 2285 | 1 |
| 2088 | ], | 2286 | ], |
| 2089 | [ | 2287 | [ |
| 2090 | "{7E9041BD-301B-4B5B-9456-1C92050E4914},1", | 2288 | "{0F6ABB5E-A021-4E1D-B187-7A72E74000DC},1", |
| 2091 | 1 | 2289 | 1 |
| 2092 | ], | 2290 | ], |
| 2093 | [ | 2291 | [ |
| 2094 | "{EFA77BAE-8A98-4176-B536-B615D1D2ED2B},1", | 2292 | "{F9DA3139-5856-4B11-95C0-356683E3ACFE},1", |
| 2095 | 1 | 2293 | 1 |
| 2096 | ], | 2294 | ], |
| 2097 | [ | 2295 | [ |
| 2098 | "{87AC0239-51A5-47C6-BAF4-926CCF56385C},1", | 2296 | "{439B80E0-9D4C-4A2F-9163-4E301491364D},1", |
| 2099 | 1 | 2297 | 1 |
| 2100 | ], | 2298 | ], |
| 2101 | [ | 2299 | [ |
| 2102 | "{C50BF7FC-F848-4B91-B8C8-743D0AA5F195},1", | 2300 | "{B03B973C-50BC-4B9A-9914-B1D5C0CF6197},1", |
| 2103 | 1 | 2301 | 1 |
| 2104 | ], | 2302 | ], |
| 2105 | [ | 2303 | [ |
| 2106 | "{FD8D07D0-C447-4516-A7DB-F8522B77D4A7},1", | 2304 | "{A459C474-E6EB-4BE5-9193-271799B2860B},1", |
| 2107 | 1 | 2305 | 1 |
| 2108 | ], | 2306 | ], |
| 2109 | [ | 2307 | [ |
| 2110 | "{D2E1FE57-F45D-49C3-B388-95222ACA3886},1", | 2308 | "{4ED14CEE-8560-4125-B488-9945C116741E},1", |
| 2111 | 1 | 2309 | 1 |
| 2112 | ], | 2310 | ], |
| 2113 | [ | 2311 | [ |
| 2114 | "{7F6702B3-C977-436F-82AF-403E2BE950E7},1", | 2312 | "{C4FF8FE4-4C43-461F-98DD-EB1FF2A863B2},1", |
| 2115 | 1 | 2313 | 1 |
| 2116 | ], | 2314 | ], |
| 2117 | [ | 2315 | [ |
| 2118 | "{688C4EA1-80F5-4D85-B9BB-F8633FF3C0CA},1", | 2316 | "{932BE37F-05AA-4EFB-85D8-A33445E738B3},1", |
| 2119 | 1 | 2317 | 1 |
| 2120 | ], | 2318 | ], |
| 2121 | [ | 2319 | [ |
| 2122 | "{754FA957-C432-4502-A5CF-EE44449462C0},1", | 2320 | "{9B755368-1D49-4D7E-8D33-6DC536F57848},1", |
| 2123 | 1 | 2321 | 1 |
| 2124 | ], | 2322 | ], |
| 2125 | [ | 2323 | [ |
| 2126 | "{D2197C55-D962-4DF0-8B14-26F88C06D6FA},1", | 2324 | "{D045EAAF-ACE9-4B4F-979B-E348A4ADE693},1", |
| 2127 | 1 | 2325 | 1 |
| 2128 | ], | 2326 | ], |
| 2129 | [ | 2327 | [ |
| 2130 | "{87B83BA8-A906-4F71-A6CA-3E8133A75D34},1", | 2328 | "{5CA4C59F-B400-4BC6-8B90-30ECAC35CB43},1", |
| 2131 | 1 | 2329 | 1 |
| 2132 | ], | 2330 | ], |
| 2133 | [ | 2331 | [ |
| 2134 | "{F2D6AEBB-98EA-4F10-991F-5243E74AF450},1", | 2332 | "{D63FB383-FB37-44F9-AEBB-E868E903A2BB},1", |
| 2135 | 1 | 2333 | 1 |
| 2136 | ], | 2334 | ], |
| 2137 | [ | 2335 | [ |
| 2138 | "{406687D7-F9E9-491B-85DA-166DE6CD5A60},1", | 2336 | "{C6E54364-6CD0-4764-8180-873030B9EA53},1", |
| 2139 | 1 | 2337 | 1 |
| 2140 | ], | 2338 | ], |
| 2141 | [ | 2339 | [ |
| 2142 | "{1F73BF36-9FC2-495D-844D-B370D5C47DE7},1", | 2340 | "{DEEC391B-F2D1-4EB1-BEB1-57598DFBB982},1", |
| 2143 | 1 | 2341 | 1 |
| 2144 | ], | 2342 | ], |
| 2145 | [ | 2343 | [ |
| 2146 | "{E9BD343F-4A66-4DE2-81F9-3789CCA22818},1", | 2344 | "{9E7616ED-DE19-4625-8EA0-0C5DEB18B690},1", |
| 2147 | 1 | 2345 | 1 |
| 2148 | ], | 2346 | ], |
| 2149 | [ | 2347 | [ |
| 2150 | "{189EA744-BE13-467A-95BA-2667A134C0B0},1", | 2348 | "{89F1748D-6749-4AD0-BAF6-07E9F9C058E5},1", |
| 2151 | 1 | 2349 | 1 |
| 2152 | ], | 2350 | ], |
| 2153 | [ | 2351 | [ |
| 2154 | "{017F5881-F818-4C7C-B9F9-6BCB4085F1D0},1", | 2352 | "{00B10C58-6649-428D-A081-3EBBA6E88E97},1", |
| 2155 | 1 | 2353 | 1 |
| 2156 | ], | 2354 | ], |
| 2157 | [ | 2355 | [ |
| 2158 | "{CD0D17F4-25D8-4076-9AF7-47D9CDEFCF0A},1", | 2356 | "{58E99482-710A-4938-8CFB-88A1C9B946C9},1", |
| 2159 | 1 | 2357 | 1 |
| 2160 | ], | 2358 | ], |
| 2161 | [ | 2359 | [ |
| 2162 | "{CCBB5969-BCE8-498B-BE1A-98FD3A44DB1A},1", | 2360 | "{F721C9D5-9A6A-405F-94C9-9B85A7EEB980},1", |
| 2163 | 1 | 2361 | 1 |
| 2164 | ], | 2362 | ], |
| 2165 | [ | 2363 | [ |
| 2166 | "{2B736B19-2D9C-4E76-A732-478D533D717A},1", | 2364 | "{15A45C01-A9E8-42E2-A267-C7FCE728815E},1", |
| 2167 | 1 | 2365 | 1 |
| 2168 | ] | 2366 | ] |
| 2169 | ] | 2367 | ] |
| 2170 | } | 2368 | } |
| 2171 | }, | 2369 | }, |
| 2172 | "space": "{90FCDB0C-36AE-422F-B4FE-67676826FE36},1" | 2370 | "space": "{A5A2AFBE-1DF1-4190-AAC7-1AD29C9564F2},1" |
| 2173 | }, | 2371 | }, |
| 2174 | { | 2372 | { |
| 2175 | "id": 2, | 2373 | "id": 2, |
| 2176 | "operation": { | 2374 | "operation": { |
| 2177 | "Page": { | 2375 | "Page": { |
| 2178 | "after": { | 2376 | "after": { |
| 2179 | "created": 134335976560000000, | 2377 | "created": 134348779890000000, |
| 2180 | "definitions": { | 2378 | "definitions": { |
| 2181 | "{ED9769DD-99D6-4BE2-B980-580DA9C4DFCE},18": { | 2379 | "{3E966EF8-B7B4-483F-B241-8F21DF318AB3},18": { |
| 2182 | "format": { | 2380 | "format": { |
| 2183 | "alignment": null, | 2381 | "alignment": null, |
| 2184 | "bold": null, | 2382 | "bold": null, |
| ... | @@ -2195,6 +2393,7 @@ | ... | @@ -2195,6 +2393,7 @@ |
| 2195 | "line_spacing": null, | 2393 | "line_spacing": null, |
| 2196 | "list_spacing": null, | 2394 | "list_spacing": null, |
| 2197 | "math": null, | 2395 | "math": null, |
| 2396 | "math_object": null, | ||
| 2198 | "rtl": null, | 2397 | "rtl": null, |
| 2199 | "space_after": null, | 2398 | "space_after": null, |
| 2200 | "space_before": null, | 2399 | "space_before": null, |
| ... | @@ -2209,6 +2408,24 @@ | ... | @@ -2209,6 +2408,24 @@ |
| 2209 | } | 2408 | } |
| 2210 | } | 2409 | } |
| 2211 | }, | 2410 | }, |
| 2411 | "identity": [ | ||
| 2412 | 248, | ||
| 2413 | 110, | ||
| 2414 | 150, | ||
| 2415 | 62, | ||
| 2416 | 180, | ||
| 2417 | 183, | ||
| 2418 | 63, | ||
| 2419 | 72, | ||
| 2420 | 178, | ||
| 2421 | 65, | ||
| 2422 | 143, | ||
| 2423 | 33, | ||
| 2424 | 223, | ||
| 2425 | 49, | ||
| 2426 | 138, | ||
| 2427 | 179 | ||
| 2428 | ], | ||
| 2212 | "margin_origin": [ | 2429 | "margin_origin": [ |
| 2213 | 0.0, | 2430 | 0.0, |
| 2214 | 0.0 | 2431 | 0.0 |
| ... | @@ -2216,7 +2433,7 @@ | ... | @@ -2216,7 +2433,7 @@ |
| 2216 | "objects": [ | 2433 | "objects": [ |
| 2217 | { | 2434 | { |
| 2218 | "Outline": { | 2435 | "Outline": { |
| 2219 | "id": "{930570FE-0B42-4598-A6B7-74E90CE526D4},0", | 2436 | "id": "{91379CE6-91DA-48BA-B306-77E4CEB068B6},11", |
| 2220 | "indents": [], | 2437 | "indents": [], |
| 2221 | "layout": { | 2438 | "layout": { |
| 2222 | "max_height": null, | 2439 | "max_height": null, |
| ... | @@ -2233,7 +2450,7 @@ | ... | @@ -2233,7 +2450,7 @@ |
| 2233 | "content": { | 2450 | "content": { |
| 2234 | "Text": { | 2451 | "Text": { |
| 2235 | "date_field": null, | 2452 | "date_field": null, |
| 2236 | "id": "{917ABDDE-781F-45C9-90B5-A63A0FBCAEE4},0", | 2453 | "id": "{91379CE6-91DA-48BA-B306-77E4CEB068B6},10", |
| 2237 | "tags": [], | 2454 | "tags": [], |
| 2238 | "text": { | 2455 | "text": { |
| 2239 | "spans": [ | 2456 | "spans": [ |
| ... | @@ -2255,6 +2472,7 @@ | ... | @@ -2255,6 +2472,7 @@ |
| 2255 | "line_spacing": null, | 2472 | "line_spacing": null, |
| 2256 | "list_spacing": null, | 2473 | "list_spacing": null, |
| 2257 | "math": null, | 2474 | "math": null, |
| 2475 | "math_object": null, | ||
| 2258 | "rtl": null, | 2476 | "rtl": null, |
| 2259 | "space_after": null, | 2477 | "space_after": null, |
| 2260 | "space_before": null, | 2478 | "space_before": null, |
| ... | @@ -2285,6 +2503,7 @@ | ... | @@ -2285,6 +2503,7 @@ |
| 2285 | "line_spacing": null, | 2503 | "line_spacing": null, |
| 2286 | "list_spacing": null, | 2504 | "list_spacing": null, |
| 2287 | "math": null, | 2505 | "math": null, |
| 2506 | "math_object": null, | ||
| 2288 | "rtl": null, | 2507 | "rtl": null, |
| 2289 | "space_after": null, | 2508 | "space_after": null, |
| 2290 | "space_before": null, | 2509 | "space_before": null, |
| ... | @@ -2293,9 +2512,13 @@ | ... | @@ -2293,9 +2512,13 @@ |
| 2293 | "superscript": null, | 2512 | "superscript": null, |
| 2294 | "underline": null | 2513 | "underline": null |
| 2295 | }, | 2514 | }, |
| 2296 | "id": "{A4C1F94C-C7D9-4C9A-8F97-D7437FF3F499},0", | 2515 | "id": "{91379CE6-91DA-48BA-B306-77E4CEB068B6},12", |
| 2297 | "level": 1, | 2516 | "level": 1, |
| 2298 | "lists": [], | 2517 | "lists": [], |
| 2518 | "media": { | ||
| 2519 | "recordings": [], | ||
| 2520 | "time_ms": null | ||
| 2521 | }, | ||
| 2299 | "parent": null, | 2522 | "parent": null, |
| 2300 | "style": null, | 2523 | "style": null, |
| 2301 | "tags": [] | 2524 | "tags": [] |
| ... | @@ -2308,7 +2531,7 @@ | ... | @@ -2308,7 +2531,7 @@ |
| 2308 | { | 2531 | { |
| 2309 | "Title": { | 2532 | "Title": { |
| 2310 | "date": null, | 2533 | "date": null, |
| 2311 | "id": "{ED9769DD-99D6-4BE2-B980-580DA9C4DFCE},13", | 2534 | "id": "{3E966EF8-B7B4-483F-B241-8F21DF318AB3},13", |
| 2312 | "layout": { | 2535 | "layout": { |
| 2313 | "max_height": null, | 2536 | "max_height": null, |
| 2314 | "max_width": null, | 2537 | "max_width": null, |
| ... | @@ -2319,24 +2542,29 @@ | ... | @@ -2319,24 +2542,29 @@ |
| 2319 | }, | 2542 | }, |
| 2320 | "outlines": [ | 2543 | "outlines": [ |
| 2321 | { | 2544 | { |
| 2322 | "id": "{ED9769DD-99D6-4BE2-B980-580DA9C4DFCE},14", | 2545 | "id": "{3E966EF8-B7B4-483F-B241-8F21DF318AB3},14", |
| 2323 | "indents": [], | 2546 | "indents": [ |
| 2547 | 18.0, | ||
| 2548 | 0.0, | ||
| 2549 | 27.0, | ||
| 2550 | 27.0 | ||
| 2551 | ], | ||
| 2324 | "layout": { | 2552 | "layout": { |
| 2325 | "max_height": null, | 2553 | "max_height": 21.600000381469727, |
| 2326 | "max_width": null, | 2554 | "max_width": null, |
| 2327 | "reserved_width": null, | 2555 | "reserved_width": null, |
| 2328 | "width_set_by_user": null, | 2556 | "width_set_by_user": null, |
| 2329 | "x": null, | 2557 | "x": null, |
| 2330 | "y": null | 2558 | "y": null |
| 2331 | }, | 2559 | }, |
| 2332 | "min_width": null, | 2560 | "min_width": 162.0, |
| 2333 | "paragraphs": [ | 2561 | "paragraphs": [ |
| 2334 | { | 2562 | { |
| 2335 | "collapsed": false, | 2563 | "collapsed": false, |
| 2336 | "content": { | 2564 | "content": { |
| 2337 | "Text": { | 2565 | "Text": { |
| 2338 | "date_field": null, | 2566 | "date_field": null, |
| 2339 | "id": "{ED9769DD-99D6-4BE2-B980-580DA9C4DFCE},16", | 2567 | "id": "{3E966EF8-B7B4-483F-B241-8F21DF318AB3},16", |
| 2340 | "tags": [], | 2568 | "tags": [], |
| 2341 | "text": { | 2569 | "text": { |
| 2342 | "spans": [ | 2570 | "spans": [ |
| ... | @@ -2358,6 +2586,7 @@ | ... | @@ -2358,6 +2586,7 @@ |
| 2358 | "line_spacing": null, | 2586 | "line_spacing": null, |
| 2359 | "list_spacing": null, | 2587 | "list_spacing": null, |
| 2360 | "math": null, | 2588 | "math": null, |
| 2589 | "math_object": null, | ||
| 2361 | "rtl": null, | 2590 | "rtl": null, |
| 2362 | "space_after": null, | 2591 | "space_after": null, |
| 2363 | "space_before": null, | 2592 | "space_before": null, |
| ... | @@ -2388,6 +2617,7 @@ | ... | @@ -2388,6 +2617,7 @@ |
| 2388 | "line_spacing": null, | 2617 | "line_spacing": null, |
| 2389 | "list_spacing": null, | 2618 | "list_spacing": null, |
| 2390 | "math": null, | 2619 | "math": null, |
| 2620 | "math_object": null, | ||
| 2391 | "rtl": null, | 2621 | "rtl": null, |
| 2392 | "space_after": null, | 2622 | "space_after": null, |
| 2393 | "space_before": null, | 2623 | "space_before": null, |
| ... | @@ -2396,15 +2626,19 @@ | ... | @@ -2396,15 +2626,19 @@ |
| 2396 | "superscript": null, | 2626 | "superscript": null, |
| 2397 | "underline": null | 2627 | "underline": null |
| 2398 | }, | 2628 | }, |
| 2399 | "id": "{ED9769DD-99D6-4BE2-B980-580DA9C4DFCE},15", | 2629 | "id": "{3E966EF8-B7B4-483F-B241-8F21DF318AB3},15", |
| 2400 | "level": 1, | 2630 | "level": 1, |
| 2401 | "lists": [], | 2631 | "lists": [], |
| 2632 | "media": { | ||
| 2633 | "recordings": [], | ||
| 2634 | "time_ms": null | ||
| 2635 | }, | ||
| 2402 | "parent": null, | 2636 | "parent": null, |
| 2403 | "style": "{ED9769DD-99D6-4BE2-B980-580DA9C4DFCE},18", | 2637 | "style": "{3E966EF8-B7B4-483F-B241-8F21DF318AB3},18", |
| 2404 | "tags": [] | 2638 | "tags": [] |
| 2405 | } | 2639 | } |
| 2406 | ], | 2640 | ], |
| 2407 | "title": false, | 2641 | "title": true, |
| 2408 | "unsupported": [] | 2642 | "unsupported": [] |
| 2409 | } | 2643 | } |
| 2410 | ] | 2644 | ] |
| ... | @@ -2415,9 +2649,9 @@ | ... | @@ -2415,9 +2649,9 @@ |
| 2415 | }, | 2649 | }, |
| 2416 | "author": "Author", | 2650 | "author": "Author", |
| 2417 | "before": { | 2651 | "before": { |
| 2418 | "created": 134335976560000000, | 2652 | "created": 134348779890000000, |
| 2419 | "definitions": { | 2653 | "definitions": { |
| 2420 | "{ED9769DD-99D6-4BE2-B980-580DA9C4DFCE},18": { | 2654 | "{3E966EF8-B7B4-483F-B241-8F21DF318AB3},18": { |
| 2421 | "format": { | 2655 | "format": { |
| 2422 | "alignment": null, | 2656 | "alignment": null, |
| 2423 | "bold": null, | 2657 | "bold": null, |
| ... | @@ -2434,6 +2668,7 @@ | ... | @@ -2434,6 +2668,7 @@ |
| 2434 | "line_spacing": null, | 2668 | "line_spacing": null, |
| 2435 | "list_spacing": null, | 2669 | "list_spacing": null, |
| 2436 | "math": null, | 2670 | "math": null, |
| 2671 | "math_object": null, | ||
| 2437 | "rtl": null, | 2672 | "rtl": null, |
| 2438 | "space_after": null, | 2673 | "space_after": null, |
| 2439 | "space_before": null, | 2674 | "space_before": null, |
| ... | @@ -2448,6 +2683,24 @@ | ... | @@ -2448,6 +2683,24 @@ |
| 2448 | } | 2683 | } |
| 2449 | } | 2684 | } |
| 2450 | }, | 2685 | }, |
| 2686 | "identity": [ | ||
| 2687 | 248, | ||
| 2688 | 110, | ||
| 2689 | 150, | ||
| 2690 | 62, | ||
| 2691 | 180, | ||
| 2692 | 183, | ||
| 2693 | 63, | ||
| 2694 | 72, | ||
| 2695 | 178, | ||
| 2696 | 65, | ||
| 2697 | 143, | ||
| 2698 | 33, | ||
| 2699 | 223, | ||
| 2700 | 49, | ||
| 2701 | 138, | ||
| 2702 | 179 | ||
| 2703 | ], | ||
| 2451 | "margin_origin": [ | 2704 | "margin_origin": [ |
| 2452 | 0.0, | 2705 | 0.0, |
| 2453 | 0.0 | 2706 | 0.0 |
| ... | @@ -2456,7 +2709,7 @@ | ... | @@ -2456,7 +2709,7 @@ |
| 2456 | { | 2709 | { |
| 2457 | "Title": { | 2710 | "Title": { |
| 2458 | "date": null, | 2711 | "date": null, |
| 2459 | "id": "{ED9769DD-99D6-4BE2-B980-580DA9C4DFCE},13", | 2712 | "id": "{3E966EF8-B7B4-483F-B241-8F21DF318AB3},13", |
| 2460 | "layout": { | 2713 | "layout": { |
| 2461 | "max_height": null, | 2714 | "max_height": null, |
| 2462 | "max_width": null, | 2715 | "max_width": null, |
| ... | @@ -2467,24 +2720,29 @@ | ... | @@ -2467,24 +2720,29 @@ |
| 2467 | }, | 2720 | }, |
| 2468 | "outlines": [ | 2721 | "outlines": [ |
| 2469 | { | 2722 | { |
| 2470 | "id": "{ED9769DD-99D6-4BE2-B980-580DA9C4DFCE},14", | 2723 | "id": "{3E966EF8-B7B4-483F-B241-8F21DF318AB3},14", |
| 2471 | "indents": [], | 2724 | "indents": [ |
| 2725 | 18.0, | ||
| 2726 | 0.0, | ||
| 2727 | 27.0, | ||
| 2728 | 27.0 | ||
| 2729 | ], | ||
| 2472 | "layout": { | 2730 | "layout": { |
| 2473 | "max_height": null, | 2731 | "max_height": 21.600000381469727, |
| 2474 | "max_width": null, | 2732 | "max_width": null, |
| 2475 | "reserved_width": null, | 2733 | "reserved_width": null, |
| 2476 | "width_set_by_user": null, | 2734 | "width_set_by_user": null, |
| 2477 | "x": null, | 2735 | "x": null, |
| 2478 | "y": null | 2736 | "y": null |
| 2479 | }, | 2737 | }, |
| 2480 | "min_width": null, | 2738 | "min_width": 162.0, |
| 2481 | "paragraphs": [ | 2739 | "paragraphs": [ |
| 2482 | { | 2740 | { |
| 2483 | "collapsed": false, | 2741 | "collapsed": false, |
| 2484 | "content": { | 2742 | "content": { |
| 2485 | "Text": { | 2743 | "Text": { |
| 2486 | "date_field": null, | 2744 | "date_field": null, |
| 2487 | "id": "{ED9769DD-99D6-4BE2-B980-580DA9C4DFCE},16", | 2745 | "id": "{3E966EF8-B7B4-483F-B241-8F21DF318AB3},16", |
| 2488 | "tags": [], | 2746 | "tags": [], |
| 2489 | "text": { | 2747 | "text": { |
| 2490 | "spans": [ | 2748 | "spans": [ |
| ... | @@ -2506,6 +2764,7 @@ | ... | @@ -2506,6 +2764,7 @@ |
| 2506 | "line_spacing": null, | 2764 | "line_spacing": null, |
| 2507 | "list_spacing": null, | 2765 | "list_spacing": null, |
| 2508 | "math": null, | 2766 | "math": null, |
| 2767 | "math_object": null, | ||
| 2509 | "rtl": null, | 2768 | "rtl": null, |
| 2510 | "space_after": null, | 2769 | "space_after": null, |
| 2511 | "space_before": null, | 2770 | "space_before": null, |
| ... | @@ -2536,6 +2795,7 @@ | ... | @@ -2536,6 +2795,7 @@ |
| 2536 | "line_spacing": null, | 2795 | "line_spacing": null, |
| 2537 | "list_spacing": null, | 2796 | "list_spacing": null, |
| 2538 | "math": null, | 2797 | "math": null, |
| 2798 | "math_object": null, | ||
| 2539 | "rtl": null, | 2799 | "rtl": null, |
| 2540 | "space_after": null, | 2800 | "space_after": null, |
| 2541 | "space_before": null, | 2801 | "space_before": null, |
| ... | @@ -2544,15 +2804,19 @@ | ... | @@ -2544,15 +2804,19 @@ |
| 2544 | "superscript": null, | 2804 | "superscript": null, |
| 2545 | "underline": null | 2805 | "underline": null |
| 2546 | }, | 2806 | }, |
| 2547 | "id": "{ED9769DD-99D6-4BE2-B980-580DA9C4DFCE},15", | 2807 | "id": "{3E966EF8-B7B4-483F-B241-8F21DF318AB3},15", |
| 2548 | "level": 1, | 2808 | "level": 1, |
| 2549 | "lists": [], | 2809 | "lists": [], |
| 2810 | "media": { | ||
| 2811 | "recordings": [], | ||
| 2812 | "time_ms": null | ||
| 2813 | }, | ||
| 2550 | "parent": null, | 2814 | "parent": null, |
| 2551 | "style": "{ED9769DD-99D6-4BE2-B980-580DA9C4DFCE},18", | 2815 | "style": "{3E966EF8-B7B4-483F-B241-8F21DF318AB3},18", |
| 2552 | "tags": [] | 2816 | "tags": [] |
| 2553 | } | 2817 | } |
| 2554 | ], | 2818 | ], |
| 2555 | "title": false, | 2819 | "title": true, |
| 2556 | "unsupported": [] | 2820 | "unsupported": [] |
| 2557 | } | 2821 | } |
| 2558 | ] | 2822 | ] |
| ... | @@ -2563,7 +2827,7 @@ | ... | @@ -2563,7 +2827,7 @@ |
| 2563 | } | 2827 | } |
| 2564 | } | 2828 | } |
| 2565 | }, | 2829 | }, |
| 2566 | "space": "{ED9769DD-99D6-4BE2-B980-580DA9C4DFCE},1" | 2830 | "space": "{3E966EF8-B7B4-483F-B241-8F21DF318AB3},1" |
| 2567 | } | 2831 | } |
| 2568 | ], | 2832 | ], |
| 2569 | [ | 2833 | [ |
| ... | @@ -2574,215 +2838,215 @@ | ... | @@ -2574,215 +2838,215 @@ |
| 2574 | "edits": [ | 2838 | "edits": [ |
| 2575 | { | 2839 | { |
| 2576 | "guid": [ | 2840 | "guid": [ |
| 2577 | 240, | 2841 | 164, |
| 2578 | 213, | 2842 | 233, |
| 2579 | 212, | 2843 | 248, |
| 2580 | 204, | 2844 | 53, |
| 2581 | 25, | 2845 | 50, |
| 2582 | 156, | 2846 | 163, |
| 2583 | 182, | 2847 | 52, |
| 2584 | 67, | 2848 | 65, |
| 2585 | 143, | 2849 | 145, |
| 2586 | 206, | 2850 | 221, |
| 2587 | 96, | 2851 | 232, |
| 2588 | 209, | 2852 | 231, |
| 2589 | 2, | 2853 | 172, |
| 2590 | 198, | 2854 | 110, |
| 2591 | 208, | 2855 | 51, |
| 2592 | 94 | 2856 | 85 |
| 2593 | ], | 2857 | ], |
| 2594 | "level": 1, | 2858 | "level": 1, |
| 2595 | "position": { | 2859 | "position": { |
| 2596 | "Before": "{4C5FBA5B-E348-4750-ACE4-E9694D527BFF},1" | 2860 | "Before": "{AE89305A-73F4-46B5-B41D-18884685D896},1" |
| 2597 | }, | 2861 | }, |
| 2598 | "space": "{BD0EF658-4D0F-42A7-B546-B4F4A77D246B},1" | 2862 | "space": "{551D2637-2AA7-4923-B2DD-31C2E1489FD2},1" |
| 2599 | }, | 2863 | }, |
| 2600 | { | 2864 | { |
| 2601 | "guid": [ | 2865 | "guid": [ |
| 2602 | 82, | 2866 | 127, |
| 2603 | 173, | 2867 | 137, |
| 2604 | 19, | 2868 | 251, |
| 2605 | 143, | 2869 | 136, |
| 2606 | 3, | 2870 | 118, |
| 2607 | 17, | ||
| 2608 | 177, | ||
| 2609 | 66, | ||
| 2610 | 183, | ||
| 2611 | 218, | ||
| 2612 | 131, | ||
| 2613 | 72, | 2871 | 72, |
| 2614 | 199, | 2872 | 18, |
| 2615 | 191, | 2873 | 67, |
| 2874 | 156, | ||
| 2616 | 233, | 2875 | 233, |
| 2617 | 214 | 2876 | 237, |
| 2877 | 236, | ||
| 2878 | 165, | ||
| 2879 | 97, | ||
| 2880 | 49, | ||
| 2881 | 92 | ||
| 2618 | ], | 2882 | ], |
| 2619 | "level": 2, | 2883 | "level": 2, |
| 2620 | "position": "Keep", | 2884 | "position": "Keep", |
| 2621 | "space": "{6FEA70FA-1451-4A27-8A8E-C4CC3B713CF6},1" | 2885 | "space": "{CBF82C86-3A8B-457F-92A4-1AC7BF436CE9},1" |
| 2622 | } | 2886 | } |
| 2623 | ], | 2887 | ], |
| 2624 | "observed": [ | 2888 | "observed": [ |
| 2625 | [ | 2889 | [ |
| 2626 | "{869D5929-6E5E-4EDB-9639-4C56977DD447},1", | 2890 | "{0B37E922-551A-48F8-BCAC-4AB3503B4D28},1", |
| 2627 | 1 | 2891 | 1 |
| 2628 | ], | 2892 | ], |
| 2629 | [ | 2893 | [ |
| 2630 | "{B0B53F94-0105-4C59-B65B-9473F49BFF41},1", | 2894 | "{74492C4A-164B-40E9-B297-4D3B4685347D},1", |
| 2631 | 1 | 2895 | 1 |
| 2632 | ], | 2896 | ], |
| 2633 | [ | 2897 | [ |
| 2634 | "{0E98EE82-B78F-4872-8BD9-5D9B23A817C5},1", | 2898 | "{DAFF533D-608B-4735-BA1D-2354E8CB8A69},1", |
| 2635 | 1 | 2899 | 1 |
| 2636 | ], | 2900 | ], |
| 2637 | [ | 2901 | [ |
| 2638 | "{C837A033-F055-4455-B45F-17D82016921C},1", | 2902 | "{8B766F11-2A4F-4D95-8473-D3CA68129E11},1", |
| 2639 | 1 | 2903 | 1 |
| 2640 | ], | 2904 | ], |
| 2641 | [ | 2905 | [ |
| 2642 | "{89D86121-BF5C-418B-BB92-CBFFA8EF545F},1", | 2906 | "{28E0CC5F-68BC-4A86-9ED7-114FFD3BAE9A},1", |
| 2643 | 1 | 2907 | 1 |
| 2644 | ], | 2908 | ], |
| 2645 | [ | 2909 | [ |
| 2646 | "{57E835EA-2799-4118-B55C-7BB1E51C6941},1", | 2910 | "{31FFA5F9-C1B8-412D-A2D6-7ACC3CC16FD0},1", |
| 2647 | 1 | 2911 | 1 |
| 2648 | ], | 2912 | ], |
| 2649 | [ | 2913 | [ |
| 2650 | "{F7EECCCF-B6B4-44F2-A543-36837EC18B8A},1", | 2914 | "{AFCD09F6-1085-4582-BDB2-4B91E15D2088},1", |
| 2651 | 1 | 2915 | 1 |
| 2652 | ], | 2916 | ], |
| 2653 | [ | 2917 | [ |
| 2654 | "{361A0F09-EA63-4F2D-9620-C061D4E4707E},1", | 2918 | "{A94D5882-C902-4CAE-B38E-2ED56EEF177F},1", |
| 2655 | 1 | 2919 | 1 |
| 2656 | ], | 2920 | ], |
| 2657 | [ | 2921 | [ |
| 2658 | "{EB3940F1-F255-42BE-8C31-DD09E86DBAAF},1", | 2922 | "{5AB37A0F-5702-4C8E-A8C6-79ED6ED1E69F},1", |
| 2659 | 1 | 2923 | 1 |
| 2660 | ], | 2924 | ], |
| 2661 | [ | 2925 | [ |
| 2662 | "{C2870D07-8ADC-44BB-9F12-15D6C3708689},1", | 2926 | "{F212795E-0E4B-4844-A8A6-ABD7F7025124},1", |
| 2663 | 1 | 2927 | 1 |
| 2664 | ], | 2928 | ], |
| 2665 | [ | 2929 | [ |
| 2666 | "{1E0C6331-32C9-4EF9-8087-150A8669FA91},1", | 2930 | "{1786F2CA-0A43-4081-8B55-B207EEE0331C},1", |
| 2667 | 1 | 2931 | 1 |
| 2668 | ], | 2932 | ], |
| 2669 | [ | 2933 | [ |
| 2670 | "{ED9769DD-99D6-4BE2-B980-580DA9C4DFCE},1", | 2934 | "{3E966EF8-B7B4-483F-B241-8F21DF318AB3},1", |
| 2671 | 1 | 2935 | 1 |
| 2672 | ], | 2936 | ], |
| 2673 | [ | 2937 | [ |
| 2674 | "{17C30054-7129-4A10-9D00-4D5545E4F4BA},1", | 2938 | "{0D361C76-92FD-4B76-901B-809C2C3C3CE6},1", |
| 2675 | 1 | 2939 | 1 |
| 2676 | ], | 2940 | ], |
| 2677 | [ | 2941 | [ |
| 2678 | "{4C5FBA5B-E348-4750-ACE4-E9694D527BFF},1", | 2942 | "{AE89305A-73F4-46B5-B41D-18884685D896},1", |
| 2679 | 1 | 2943 | 1 |
| 2680 | ], | 2944 | ], |
| 2681 | [ | 2945 | [ |
| 2682 | "{BD0EF658-4D0F-42A7-B546-B4F4A77D246B},1", | 2946 | "{551D2637-2AA7-4923-B2DD-31C2E1489FD2},1", |
| 2683 | 1 | 2947 | 1 |
| 2684 | ], | 2948 | ], |
| 2685 | [ | 2949 | [ |
| 2686 | "{6FEA70FA-1451-4A27-8A8E-C4CC3B713CF6},1", | 2950 | "{CBF82C86-3A8B-457F-92A4-1AC7BF436CE9},1", |
| 2687 | 1 | 2951 | 1 |
| 2688 | ], | 2952 | ], |
| 2689 | [ | 2953 | [ |
| 2690 | "{68A1E0E1-7A39-4AB6-AD71-4D8A18878D79},1", | 2954 | "{42010094-E377-4A97-9395-AD45808D0552},1", |
| 2691 | 1 | 2955 | 1 |
| 2692 | ], | 2956 | ], |
| 2693 | [ | 2957 | [ |
| 2694 | "{7E9041BD-301B-4B5B-9456-1C92050E4914},1", | 2958 | "{0F6ABB5E-A021-4E1D-B187-7A72E74000DC},1", |
| 2695 | 1 | 2959 | 1 |
| 2696 | ], | 2960 | ], |
| 2697 | [ | 2961 | [ |
| 2698 | "{EFA77BAE-8A98-4176-B536-B615D1D2ED2B},1", | 2962 | "{F9DA3139-5856-4B11-95C0-356683E3ACFE},1", |
| 2699 | 1 | 2963 | 1 |
| 2700 | ], | 2964 | ], |
| 2701 | [ | 2965 | [ |
| 2702 | "{87AC0239-51A5-47C6-BAF4-926CCF56385C},1", | 2966 | "{439B80E0-9D4C-4A2F-9163-4E301491364D},1", |
| 2703 | 1 | 2967 | 1 |
| 2704 | ], | 2968 | ], |
| 2705 | [ | 2969 | [ |
| 2706 | "{C50BF7FC-F848-4B91-B8C8-743D0AA5F195},1", | 2970 | "{B03B973C-50BC-4B9A-9914-B1D5C0CF6197},1", |
| 2707 | 1 | 2971 | 1 |
| 2708 | ], | 2972 | ], |
| 2709 | [ | 2973 | [ |
| 2710 | "{FD8D07D0-C447-4516-A7DB-F8522B77D4A7},1", | 2974 | "{A459C474-E6EB-4BE5-9193-271799B2860B},1", |
| 2711 | 1 | 2975 | 1 |
| 2712 | ], | 2976 | ], |
| 2713 | [ | 2977 | [ |
| 2714 | "{D2E1FE57-F45D-49C3-B388-95222ACA3886},1", | 2978 | "{4ED14CEE-8560-4125-B488-9945C116741E},1", |
| 2715 | 1 | 2979 | 1 |
| 2716 | ], | 2980 | ], |
| 2717 | [ | 2981 | [ |
| 2718 | "{7F6702B3-C977-436F-82AF-403E2BE950E7},1", | 2982 | "{C4FF8FE4-4C43-461F-98DD-EB1FF2A863B2},1", |
| 2719 | 1 | 2983 | 1 |
| 2720 | ], | 2984 | ], |
| 2721 | [ | 2985 | [ |
| 2722 | "{688C4EA1-80F5-4D85-B9BB-F8633FF3C0CA},1", | 2986 | "{932BE37F-05AA-4EFB-85D8-A33445E738B3},1", |
| 2723 | 1 | 2987 | 1 |
| 2724 | ], | 2988 | ], |
| 2725 | [ | 2989 | [ |
| 2726 | "{754FA957-C432-4502-A5CF-EE44449462C0},1", | 2990 | "{9B755368-1D49-4D7E-8D33-6DC536F57848},1", |
| 2727 | 1 | 2991 | 1 |
| 2728 | ], | 2992 | ], |
| 2729 | [ | 2993 | [ |
| 2730 | "{D2197C55-D962-4DF0-8B14-26F88C06D6FA},1", | 2994 | "{D045EAAF-ACE9-4B4F-979B-E348A4ADE693},1", |
| 2731 | 1 | 2995 | 1 |
| 2732 | ], | 2996 | ], |
| 2733 | [ | 2997 | [ |
| 2734 | "{87B83BA8-A906-4F71-A6CA-3E8133A75D34},1", | 2998 | "{5CA4C59F-B400-4BC6-8B90-30ECAC35CB43},1", |
| 2735 | 1 | 2999 | 1 |
| 2736 | ], | 3000 | ], |
| 2737 | [ | 3001 | [ |
| 2738 | "{F2D6AEBB-98EA-4F10-991F-5243E74AF450},1", | 3002 | "{D63FB383-FB37-44F9-AEBB-E868E903A2BB},1", |
| 2739 | 1 | 3003 | 1 |
| 2740 | ], | 3004 | ], |
| 2741 | [ | 3005 | [ |
| 2742 | "{406687D7-F9E9-491B-85DA-166DE6CD5A60},1", | 3006 | "{C6E54364-6CD0-4764-8180-873030B9EA53},1", |
| 2743 | 1 | 3007 | 1 |
| 2744 | ], | 3008 | ], |
| 2745 | [ | 3009 | [ |
| 2746 | "{1F73BF36-9FC2-495D-844D-B370D5C47DE7},1", | 3010 | "{DEEC391B-F2D1-4EB1-BEB1-57598DFBB982},1", |
| 2747 | 1 | 3011 | 1 |
| 2748 | ], | 3012 | ], |
| 2749 | [ | 3013 | [ |
| 2750 | "{E9BD343F-4A66-4DE2-81F9-3789CCA22818},1", | 3014 | "{9E7616ED-DE19-4625-8EA0-0C5DEB18B690},1", |
| 2751 | 1 | 3015 | 1 |
| 2752 | ], | 3016 | ], |
| 2753 | [ | 3017 | [ |
| 2754 | "{189EA744-BE13-467A-95BA-2667A134C0B0},1", | 3018 | "{89F1748D-6749-4AD0-BAF6-07E9F9C058E5},1", |
| 2755 | 1 | 3019 | 1 |
| 2756 | ], | 3020 | ], |
| 2757 | [ | 3021 | [ |
| 2758 | "{017F5881-F818-4C7C-B9F9-6BCB4085F1D0},1", | 3022 | "{00B10C58-6649-428D-A081-3EBBA6E88E97},1", |
| 2759 | 1 | 3023 | 1 |
| 2760 | ], | 3024 | ], |
| 2761 | [ | 3025 | [ |
| 2762 | "{CD0D17F4-25D8-4076-9AF7-47D9CDEFCF0A},1", | 3026 | "{58E99482-710A-4938-8CFB-88A1C9B946C9},1", |
| 2763 | 1 | 3027 | 1 |
| 2764 | ], | 3028 | ], |
| 2765 | [ | 3029 | [ |
| 2766 | "{CCBB5969-BCE8-498B-BE1A-98FD3A44DB1A},1", | 3030 | "{F721C9D5-9A6A-405F-94C9-9B85A7EEB980},1", |
| 2767 | 1 | 3031 | 1 |
| 2768 | ], | 3032 | ], |
| 2769 | [ | 3033 | [ |
| 2770 | "{2B736B19-2D9C-4E76-A732-478D533D717A},1", | 3034 | "{15A45C01-A9E8-42E2-A267-C7FCE728815E},1", |
| 2771 | 1 | 3035 | 1 |
| 2772 | ] | 3036 | ] |
| 2773 | ] | 3037 | ] |
| 2774 | } | 3038 | } |
| 2775 | }, | 3039 | }, |
| 2776 | "space": "{90FCDB0C-36AE-422F-B4FE-67676826FE36},1" | 3040 | "space": "{A5A2AFBE-1DF1-4190-AAC7-1AD29C9564F2},1" |
| 2777 | }, | 3041 | }, |
| 2778 | { | 3042 | { |
| 2779 | "id": 2, | 3043 | "id": 2, |
| 2780 | "operation": { | 3044 | "operation": { |
| 2781 | "Page": { | 3045 | "Page": { |
| 2782 | "after": { | 3046 | "after": { |
| 2783 | "created": 134335976560000000, | 3047 | "created": 134348779890000000, |
| 2784 | "definitions": { | 3048 | "definitions": { |
| 2785 | "{BD0EF658-4D0F-42A7-B546-B4F4A77D246B},18": { | 3049 | "{551D2637-2AA7-4923-B2DD-31C2E1489FD2},18": { |
| 2786 | "format": { | 3050 | "format": { |
| 2787 | "alignment": null, | 3051 | "alignment": null, |
| 2788 | "bold": null, | 3052 | "bold": null, |
| ... | @@ -2799,6 +3063,7 @@ | ... | @@ -2799,6 +3063,7 @@ |
| 2799 | "line_spacing": null, | 3063 | "line_spacing": null, |
| 2800 | "list_spacing": null, | 3064 | "list_spacing": null, |
| 2801 | "math": null, | 3065 | "math": null, |
| 3066 | "math_object": null, | ||
| 2802 | "rtl": null, | 3067 | "rtl": null, |
| 2803 | "space_after": null, | 3068 | "space_after": null, |
| 2804 | "space_before": null, | 3069 | "space_before": null, |
| ... | @@ -2813,6 +3078,24 @@ | ... | @@ -2813,6 +3078,24 @@ |
| 2813 | } | 3078 | } |
| 2814 | } | 3079 | } |
| 2815 | }, | 3080 | }, |
| 3081 | "identity": [ | ||
| 3082 | 55, | ||
| 3083 | 38, | ||
| 3084 | 29, | ||
| 3085 | 85, | ||
| 3086 | 167, | ||
| 3087 | 42, | ||
| 3088 | 35, | ||
| 3089 | 73, | ||
| 3090 | 178, | ||
| 3091 | 221, | ||
| 3092 | 49, | ||
| 3093 | 194, | ||
| 3094 | 225, | ||
| 3095 | 72, | ||
| 3096 | 159, | ||
| 3097 | 210 | ||
| 3098 | ], | ||
| 2816 | "margin_origin": [ | 3099 | "margin_origin": [ |
| 2817 | 0.0, | 3100 | 0.0, |
| 2818 | 0.0 | 3101 | 0.0 |
| ... | @@ -2820,7 +3103,7 @@ | ... | @@ -2820,7 +3103,7 @@ |
| 2820 | "objects": [ | 3103 | "objects": [ |
| 2821 | { | 3104 | { |
| 2822 | "Outline": { | 3105 | "Outline": { |
| 2823 | "id": "{6A6E0E73-D2A2-4C82-B0B4-E8A3695B4D5A},0", | 3106 | "id": "{91379CE6-91DA-48BA-B306-77E4CEB068B6},14", |
| 2824 | "indents": [], | 3107 | "indents": [], |
| 2825 | "layout": { | 3108 | "layout": { |
| 2826 | "max_height": null, | 3109 | "max_height": null, |
| ... | @@ -2837,7 +3120,7 @@ | ... | @@ -2837,7 +3120,7 @@ |
| 2837 | "content": { | 3120 | "content": { |
| 2838 | "Text": { | 3121 | "Text": { |
| 2839 | "date_field": null, | 3122 | "date_field": null, |
| 2840 | "id": "{A11F6C01-70A2-44C7-90E9-91C455D075E7},0", | 3123 | "id": "{91379CE6-91DA-48BA-B306-77E4CEB068B6},13", |
| 2841 | "tags": [], | 3124 | "tags": [], |
| 2842 | "text": { | 3125 | "text": { |
| 2843 | "spans": [ | 3126 | "spans": [ |
| ... | @@ -2859,6 +3142,7 @@ | ... | @@ -2859,6 +3142,7 @@ |
| 2859 | "line_spacing": null, | 3142 | "line_spacing": null, |
| 2860 | "list_spacing": null, | 3143 | "list_spacing": null, |
| 2861 | "math": null, | 3144 | "math": null, |
| 3145 | "math_object": null, | ||
| 2862 | "rtl": null, | 3146 | "rtl": null, |
| 2863 | "space_after": null, | 3147 | "space_after": null, |
| 2864 | "space_before": null, | 3148 | "space_before": null, |
| ... | @@ -2889,6 +3173,7 @@ | ... | @@ -2889,6 +3173,7 @@ |
| 2889 | "line_spacing": null, | 3173 | "line_spacing": null, |
| 2890 | "list_spacing": null, | 3174 | "list_spacing": null, |
| 2891 | "math": null, | 3175 | "math": null, |
| 3176 | "math_object": null, | ||
| 2892 | "rtl": null, | 3177 | "rtl": null, |
| 2893 | "space_after": null, | 3178 | "space_after": null, |
| 2894 | "space_before": null, | 3179 | "space_before": null, |
| ... | @@ -2897,9 +3182,13 @@ | ... | @@ -2897,9 +3182,13 @@ |
| 2897 | "superscript": null, | 3182 | "superscript": null, |
| 2898 | "underline": null | 3183 | "underline": null |
| 2899 | }, | 3184 | }, |
| 2900 | "id": "{1B1F5EFD-3EE8-4031-BEB2-C3D27A332144},0", | 3185 | "id": "{91379CE6-91DA-48BA-B306-77E4CEB068B6},15", |
| 2901 | "level": 1, | 3186 | "level": 1, |
| 2902 | "lists": [], | 3187 | "lists": [], |
| 3188 | "media": { | ||
| 3189 | "recordings": [], | ||
| 3190 | "time_ms": null | ||
| 3191 | }, | ||
| 2903 | "parent": null, | 3192 | "parent": null, |
| 2904 | "style": null, | 3193 | "style": null, |
| 2905 | "tags": [] | 3194 | "tags": [] |
| ... | @@ -2912,7 +3201,7 @@ | ... | @@ -2912,7 +3201,7 @@ |
| 2912 | { | 3201 | { |
| 2913 | "Title": { | 3202 | "Title": { |
| 2914 | "date": null, | 3203 | "date": null, |
| 2915 | "id": "{BD0EF658-4D0F-42A7-B546-B4F4A77D246B},13", | 3204 | "id": "{551D2637-2AA7-4923-B2DD-31C2E1489FD2},13", |
| 2916 | "layout": { | 3205 | "layout": { |
| 2917 | "max_height": null, | 3206 | "max_height": null, |
| 2918 | "max_width": null, | 3207 | "max_width": null, |
| ... | @@ -2923,24 +3212,29 @@ | ... | @@ -2923,24 +3212,29 @@ |
| 2923 | }, | 3212 | }, |
| 2924 | "outlines": [ | 3213 | "outlines": [ |
| 2925 | { | 3214 | { |
| 2926 | "id": "{BD0EF658-4D0F-42A7-B546-B4F4A77D246B},14", | 3215 | "id": "{551D2637-2AA7-4923-B2DD-31C2E1489FD2},14", |
| 2927 | "indents": [], | 3216 | "indents": [ |
| 3217 | 18.0, | ||
| 3218 | 0.0, | ||
| 3219 | 27.0, | ||
| 3220 | 27.0 | ||
| 3221 | ], | ||
| 2928 | "layout": { | 3222 | "layout": { |
| 2929 | "max_height": null, | 3223 | "max_height": 21.600000381469727, |
| 2930 | "max_width": null, | 3224 | "max_width": null, |
| 2931 | "reserved_width": null, | 3225 | "reserved_width": null, |
| 2932 | "width_set_by_user": null, | 3226 | "width_set_by_user": null, |
| 2933 | "x": null, | 3227 | "x": null, |
| 2934 | "y": null | 3228 | "y": null |
| 2935 | }, | 3229 | }, |
| 2936 | "min_width": null, | 3230 | "min_width": 162.0, |
| 2937 | "paragraphs": [ | 3231 | "paragraphs": [ |
| 2938 | { | 3232 | { |
| 2939 | "collapsed": false, | 3233 | "collapsed": false, |
| 2940 | "content": { | 3234 | "content": { |
| 2941 | "Text": { | 3235 | "Text": { |
| 2942 | "date_field": null, | 3236 | "date_field": null, |
| 2943 | "id": "{BD0EF658-4D0F-42A7-B546-B4F4A77D246B},16", | 3237 | "id": "{551D2637-2AA7-4923-B2DD-31C2E1489FD2},16", |
| 2944 | "tags": [], | 3238 | "tags": [], |
| 2945 | "text": { | 3239 | "text": { |
| 2946 | "spans": [ | 3240 | "spans": [ |
| ... | @@ -2962,6 +3256,7 @@ | ... | @@ -2962,6 +3256,7 @@ |
| 2962 | "line_spacing": null, | 3256 | "line_spacing": null, |
| 2963 | "list_spacing": null, | 3257 | "list_spacing": null, |
| 2964 | "math": null, | 3258 | "math": null, |
| 3259 | "math_object": null, | ||
| 2965 | "rtl": null, | 3260 | "rtl": null, |
| 2966 | "space_after": null, | 3261 | "space_after": null, |
| 2967 | "space_before": null, | 3262 | "space_before": null, |
| ... | @@ -2992,6 +3287,7 @@ | ... | @@ -2992,6 +3287,7 @@ |
| 2992 | "line_spacing": null, | 3287 | "line_spacing": null, |
| 2993 | "list_spacing": null, | 3288 | "list_spacing": null, |
| 2994 | "math": null, | 3289 | "math": null, |
| 3290 | "math_object": null, | ||
| 2995 | "rtl": null, | 3291 | "rtl": null, |
| 2996 | "space_after": null, | 3292 | "space_after": null, |
| 2997 | "space_before": null, | 3293 | "space_before": null, |
| ... | @@ -3000,15 +3296,19 @@ | ... | @@ -3000,15 +3296,19 @@ |
| 3000 | "superscript": null, | 3296 | "superscript": null, |
| 3001 | "underline": null | 3297 | "underline": null |
| 3002 | }, | 3298 | }, |
| 3003 | "id": "{BD0EF658-4D0F-42A7-B546-B4F4A77D246B},15", | 3299 | "id": "{551D2637-2AA7-4923-B2DD-31C2E1489FD2},15", |
| 3004 | "level": 1, | 3300 | "level": 1, |
| 3005 | "lists": [], | 3301 | "lists": [], |
| 3302 | "media": { | ||
| 3303 | "recordings": [], | ||
| 3304 | "time_ms": null | ||
| 3305 | }, | ||
| 3006 | "parent": null, | 3306 | "parent": null, |
| 3007 | "style": "{BD0EF658-4D0F-42A7-B546-B4F4A77D246B},18", | 3307 | "style": "{551D2637-2AA7-4923-B2DD-31C2E1489FD2},18", |
| 3008 | "tags": [] | 3308 | "tags": [] |
| 3009 | } | 3309 | } |
| 3010 | ], | 3310 | ], |
| 3011 | "title": false, | 3311 | "title": true, |
| 3012 | "unsupported": [] | 3312 | "unsupported": [] |
| 3013 | } | 3313 | } |
| 3014 | ] | 3314 | ] |
| ... | @@ -3019,9 +3319,9 @@ | ... | @@ -3019,9 +3319,9 @@ |
| 3019 | }, | 3319 | }, |
| 3020 | "author": "Author", | 3320 | "author": "Author", |
| 3021 | "before": { | 3321 | "before": { |
| 3022 | "created": 134335976560000000, | 3322 | "created": 134348779890000000, |
| 3023 | "definitions": { | 3323 | "definitions": { |
| 3024 | "{BD0EF658-4D0F-42A7-B546-B4F4A77D246B},18": { | 3324 | "{551D2637-2AA7-4923-B2DD-31C2E1489FD2},18": { |
| 3025 | "format": { | 3325 | "format": { |
| 3026 | "alignment": null, | 3326 | "alignment": null, |
| 3027 | "bold": null, | 3327 | "bold": null, |
| ... | @@ -3038,6 +3338,7 @@ | ... | @@ -3038,6 +3338,7 @@ |
| 3038 | "line_spacing": null, | 3338 | "line_spacing": null, |
| 3039 | "list_spacing": null, | 3339 | "list_spacing": null, |
| 3040 | "math": null, | 3340 | "math": null, |
| 3341 | "math_object": null, | ||
| 3041 | "rtl": null, | 3342 | "rtl": null, |
| 3042 | "space_after": null, | 3343 | "space_after": null, |
| 3043 | "space_before": null, | 3344 | "space_before": null, |
| ... | @@ -3052,6 +3353,24 @@ | ... | @@ -3052,6 +3353,24 @@ |
| 3052 | } | 3353 | } |
| 3053 | } | 3354 | } |
| 3054 | }, | 3355 | }, |
| 3356 | "identity": [ | ||
| 3357 | 55, | ||
| 3358 | 38, | ||
| 3359 | 29, | ||
| 3360 | 85, | ||
| 3361 | 167, | ||
| 3362 | 42, | ||
| 3363 | 35, | ||
| 3364 | 73, | ||
| 3365 | 178, | ||
| 3366 | 221, | ||
| 3367 | 49, | ||
| 3368 | 194, | ||
| 3369 | 225, | ||
| 3370 | 72, | ||
| 3371 | 159, | ||
| 3372 | 210 | ||
| 3373 | ], | ||
| 3055 | "margin_origin": [ | 3374 | "margin_origin": [ |
| 3056 | 0.0, | 3375 | 0.0, |
| 3057 | 0.0 | 3376 | 0.0 |
| ... | @@ -3060,7 +3379,7 @@ | ... | @@ -3060,7 +3379,7 @@ |
| 3060 | { | 3379 | { |
| 3061 | "Title": { | 3380 | "Title": { |
| 3062 | "date": null, | 3381 | "date": null, |
| 3063 | "id": "{BD0EF658-4D0F-42A7-B546-B4F4A77D246B},13", | 3382 | "id": "{551D2637-2AA7-4923-B2DD-31C2E1489FD2},13", |
| 3064 | "layout": { | 3383 | "layout": { |
| 3065 | "max_height": null, | 3384 | "max_height": null, |
| 3066 | "max_width": null, | 3385 | "max_width": null, |
| ... | @@ -3071,24 +3390,29 @@ | ... | @@ -3071,24 +3390,29 @@ |
| 3071 | }, | 3390 | }, |
| 3072 | "outlines": [ | 3391 | "outlines": [ |
| 3073 | { | 3392 | { |
| 3074 | "id": "{BD0EF658-4D0F-42A7-B546-B4F4A77D246B},14", | 3393 | "id": "{551D2637-2AA7-4923-B2DD-31C2E1489FD2},14", |
| 3075 | "indents": [], | 3394 | "indents": [ |
| 3395 | 18.0, | ||
| 3396 | 0.0, | ||
| 3397 | 27.0, | ||
| 3398 | 27.0 | ||
| 3399 | ], | ||
| 3076 | "layout": { | 3400 | "layout": { |
| 3077 | "max_height": null, | 3401 | "max_height": 21.600000381469727, |
| 3078 | "max_width": null, | 3402 | "max_width": null, |
| 3079 | "reserved_width": null, | 3403 | "reserved_width": null, |
| 3080 | "width_set_by_user": null, | 3404 | "width_set_by_user": null, |
| 3081 | "x": null, | 3405 | "x": null, |
| 3082 | "y": null | 3406 | "y": null |
| 3083 | }, | 3407 | }, |
| 3084 | "min_width": null, | 3408 | "min_width": 162.0, |
| 3085 | "paragraphs": [ | 3409 | "paragraphs": [ |
| 3086 | { | 3410 | { |
| 3087 | "collapsed": false, | 3411 | "collapsed": false, |
| 3088 | "content": { | 3412 | "content": { |
| 3089 | "Text": { | 3413 | "Text": { |
| 3090 | "date_field": null, | 3414 | "date_field": null, |
| 3091 | "id": "{BD0EF658-4D0F-42A7-B546-B4F4A77D246B},16", | 3415 | "id": "{551D2637-2AA7-4923-B2DD-31C2E1489FD2},16", |
| 3092 | "tags": [], | 3416 | "tags": [], |
| 3093 | "text": { | 3417 | "text": { |
| 3094 | "spans": [ | 3418 | "spans": [ |
| ... | @@ -3110,6 +3434,7 @@ | ... | @@ -3110,6 +3434,7 @@ |
| 3110 | "line_spacing": null, | 3434 | "line_spacing": null, |
| 3111 | "list_spacing": null, | 3435 | "list_spacing": null, |
| 3112 | "math": null, | 3436 | "math": null, |
| 3437 | "math_object": null, | ||
| 3113 | "rtl": null, | 3438 | "rtl": null, |
| 3114 | "space_after": null, | 3439 | "space_after": null, |
| 3115 | "space_before": null, | 3440 | "space_before": null, |
| ... | @@ -3140,6 +3465,7 @@ | ... | @@ -3140,6 +3465,7 @@ |
| 3140 | "line_spacing": null, | 3465 | "line_spacing": null, |
| 3141 | "list_spacing": null, | 3466 | "list_spacing": null, |
| 3142 | "math": null, | 3467 | "math": null, |
| 3468 | "math_object": null, | ||
| 3143 | "rtl": null, | 3469 | "rtl": null, |
| 3144 | "space_after": null, | 3470 | "space_after": null, |
| 3145 | "space_before": null, | 3471 | "space_before": null, |
| ... | @@ -3148,15 +3474,19 @@ | ... | @@ -3148,15 +3474,19 @@ |
| 3148 | "superscript": null, | 3474 | "superscript": null, |
| 3149 | "underline": null | 3475 | "underline": null |
| 3150 | }, | 3476 | }, |
| 3151 | "id": "{BD0EF658-4D0F-42A7-B546-B4F4A77D246B},15", | 3477 | "id": "{551D2637-2AA7-4923-B2DD-31C2E1489FD2},15", |
| 3152 | "level": 1, | 3478 | "level": 1, |
| 3153 | "lists": [], | 3479 | "lists": [], |
| 3480 | "media": { | ||
| 3481 | "recordings": [], | ||
| 3482 | "time_ms": null | ||
| 3483 | }, | ||
| 3154 | "parent": null, | 3484 | "parent": null, |
| 3155 | "style": "{BD0EF658-4D0F-42A7-B546-B4F4A77D246B},18", | 3485 | "style": "{551D2637-2AA7-4923-B2DD-31C2E1489FD2},18", |
| 3156 | "tags": [] | 3486 | "tags": [] |
| 3157 | } | 3487 | } |
| 3158 | ], | 3488 | ], |
| 3159 | "title": false, | 3489 | "title": true, |
| 3160 | "unsupported": [] | 3490 | "unsupported": [] |
| 3161 | } | 3491 | } |
| 3162 | ] | 3492 | ] |
| ... | @@ -3167,7 +3497,7 @@ | ... | @@ -3167,7 +3497,7 @@ |
| 3167 | } | 3497 | } |
| 3168 | } | 3498 | } |
| 3169 | }, | 3499 | }, |
| 3170 | "space": "{BD0EF658-4D0F-42A7-B546-B4F4A77D246B},1" | 3500 | "space": "{551D2637-2AA7-4923-B2DD-31C2E1489FD2},1" |
| 3171 | } | 3501 | } |
| 3172 | ], | 3502 | ], |
| 3173 | [ | 3503 | [ |
| ... | @@ -3178,215 +3508,215 @@ | ... | @@ -3178,215 +3508,215 @@ |
| 3178 | "edits": [ | 3508 | "edits": [ |
| 3179 | { | 3509 | { |
| 3180 | "guid": [ | 3510 | "guid": [ |
| 3181 | 170, | 3511 | 2, |
| 3182 | 199, | ||
| 3183 | 91, | ||
| 3184 | 6, | ||
| 3185 | 149, | ||
| 3186 | 108, | 3512 | 108, |
| 3187 | 28, | 3513 | 104, |
| 3188 | 78, | 3514 | 0, |
| 3189 | 169, | 3515 | 158, |
| 3516 | 109, | ||
| 3517 | 176, | ||
| 3518 | 79, | ||
| 3519 | 171, | ||
| 3520 | 232, | ||
| 3521 | 110, | ||
| 3522 | 168, | ||
| 3523 | 146, | ||
| 3524 | 200, | ||
| 3190 | 241, | 3525 | 241, |
| 3191 | 201, | 3526 | 35 |
| 3192 | 191, | ||
| 3193 | 131, | ||
| 3194 | 111, | ||
| 3195 | 13, | ||
| 3196 | 167 | ||
| 3197 | ], | 3527 | ], |
| 3198 | "level": 1, | 3528 | "level": 1, |
| 3199 | "position": { | 3529 | "position": { |
| 3200 | "Before": "{68A1E0E1-7A39-4AB6-AD71-4D8A18878D79},1" | 3530 | "Before": "{42010094-E377-4A97-9395-AD45808D0552},1" |
| 3201 | }, | 3531 | }, |
| 3202 | "space": "{7E9041BD-301B-4B5B-9456-1C92050E4914},1" | 3532 | "space": "{0F6ABB5E-A021-4E1D-B187-7A72E74000DC},1" |
| 3203 | }, | 3533 | }, |
| 3204 | { | 3534 | { |
| 3205 | "guid": [ | 3535 | "guid": [ |
| 3206 | 18, | 3536 | 98, |
| 3207 | 19, | ||
| 3208 | 211, | ||
| 3209 | 10, | ||
| 3210 | 209, | ||
| 3211 | 7, | ||
| 3212 | 195, | 3537 | 195, |
| 3213 | 66, | 3538 | 206, |
| 3214 | 172, | 3539 | 208, |
| 3215 | 223, | 3540 | 54, |
| 3216 | 64, | 3541 | 190, |
| 3217 | 131, | 3542 | 72, |
| 3218 | 73, | 3543 | 65, |
| 3219 | 37, | 3544 | 153, |
| 3220 | 59, | 3545 | 87, |
| 3221 | 187 | 3546 | 208, |
| 3547 | 13, | ||
| 3548 | 10, | ||
| 3549 | 87, | ||
| 3550 | 170, | ||
| 3551 | 34 | ||
| 3222 | ], | 3552 | ], |
| 3223 | "level": 2, | 3553 | "level": 2, |
| 3224 | "position": "Keep", | 3554 | "position": "Keep", |
| 3225 | "space": "{EFA77BAE-8A98-4176-B536-B615D1D2ED2B},1" | 3555 | "space": "{F9DA3139-5856-4B11-95C0-356683E3ACFE},1" |
| 3226 | } | 3556 | } |
| 3227 | ], | 3557 | ], |
| 3228 | "observed": [ | 3558 | "observed": [ |
| 3229 | [ | 3559 | [ |
| 3230 | "{869D5929-6E5E-4EDB-9639-4C56977DD447},1", | 3560 | "{0B37E922-551A-48F8-BCAC-4AB3503B4D28},1", |
| 3231 | 1 | 3561 | 1 |
| 3232 | ], | 3562 | ], |
| 3233 | [ | 3563 | [ |
| 3234 | "{B0B53F94-0105-4C59-B65B-9473F49BFF41},1", | 3564 | "{74492C4A-164B-40E9-B297-4D3B4685347D},1", |
| 3235 | 1 | 3565 | 1 |
| 3236 | ], | 3566 | ], |
| 3237 | [ | 3567 | [ |
| 3238 | "{0E98EE82-B78F-4872-8BD9-5D9B23A817C5},1", | 3568 | "{DAFF533D-608B-4735-BA1D-2354E8CB8A69},1", |
| 3239 | 1 | 3569 | 1 |
| 3240 | ], | 3570 | ], |
| 3241 | [ | 3571 | [ |
| 3242 | "{C837A033-F055-4455-B45F-17D82016921C},1", | 3572 | "{8B766F11-2A4F-4D95-8473-D3CA68129E11},1", |
| 3243 | 1 | 3573 | 1 |
| 3244 | ], | 3574 | ], |
| 3245 | [ | 3575 | [ |
| 3246 | "{89D86121-BF5C-418B-BB92-CBFFA8EF545F},1", | 3576 | "{28E0CC5F-68BC-4A86-9ED7-114FFD3BAE9A},1", |
| 3247 | 1 | 3577 | 1 |
| 3248 | ], | 3578 | ], |
| 3249 | [ | 3579 | [ |
| 3250 | "{57E835EA-2799-4118-B55C-7BB1E51C6941},1", | 3580 | "{31FFA5F9-C1B8-412D-A2D6-7ACC3CC16FD0},1", |
| 3251 | 1 | 3581 | 1 |
| 3252 | ], | 3582 | ], |
| 3253 | [ | 3583 | [ |
| 3254 | "{F7EECCCF-B6B4-44F2-A543-36837EC18B8A},1", | 3584 | "{AFCD09F6-1085-4582-BDB2-4B91E15D2088},1", |
| 3255 | 1 | 3585 | 1 |
| 3256 | ], | 3586 | ], |
| 3257 | [ | 3587 | [ |
| 3258 | "{361A0F09-EA63-4F2D-9620-C061D4E4707E},1", | 3588 | "{A94D5882-C902-4CAE-B38E-2ED56EEF177F},1", |
| 3259 | 1 | 3589 | 1 |
| 3260 | ], | 3590 | ], |
| 3261 | [ | 3591 | [ |
| 3262 | "{EB3940F1-F255-42BE-8C31-DD09E86DBAAF},1", | 3592 | "{5AB37A0F-5702-4C8E-A8C6-79ED6ED1E69F},1", |
| 3263 | 1 | 3593 | 1 |
| 3264 | ], | 3594 | ], |
| 3265 | [ | 3595 | [ |
| 3266 | "{C2870D07-8ADC-44BB-9F12-15D6C3708689},1", | 3596 | "{F212795E-0E4B-4844-A8A6-ABD7F7025124},1", |
| 3267 | 1 | 3597 | 1 |
| 3268 | ], | 3598 | ], |
| 3269 | [ | 3599 | [ |
| 3270 | "{1E0C6331-32C9-4EF9-8087-150A8669FA91},1", | 3600 | "{1786F2CA-0A43-4081-8B55-B207EEE0331C},1", |
| 3271 | 1 | 3601 | 1 |
| 3272 | ], | 3602 | ], |
| 3273 | [ | 3603 | [ |
| 3274 | "{ED9769DD-99D6-4BE2-B980-580DA9C4DFCE},1", | 3604 | "{3E966EF8-B7B4-483F-B241-8F21DF318AB3},1", |
| 3275 | 1 | 3605 | 1 |
| 3276 | ], | 3606 | ], |
| 3277 | [ | 3607 | [ |
| 3278 | "{17C30054-7129-4A10-9D00-4D5545E4F4BA},1", | 3608 | "{0D361C76-92FD-4B76-901B-809C2C3C3CE6},1", |
| 3279 | 1 | 3609 | 1 |
| 3280 | ], | 3610 | ], |
| 3281 | [ | 3611 | [ |
| 3282 | "{4C5FBA5B-E348-4750-ACE4-E9694D527BFF},1", | 3612 | "{AE89305A-73F4-46B5-B41D-18884685D896},1", |
| 3283 | 1 | 3613 | 1 |
| 3284 | ], | 3614 | ], |
| 3285 | [ | 3615 | [ |
| 3286 | "{BD0EF658-4D0F-42A7-B546-B4F4A77D246B},1", | 3616 | "{551D2637-2AA7-4923-B2DD-31C2E1489FD2},1", |
| 3287 | 1 | 3617 | 1 |
| 3288 | ], | 3618 | ], |
| 3289 | [ | 3619 | [ |
| 3290 | "{6FEA70FA-1451-4A27-8A8E-C4CC3B713CF6},1", | 3620 | "{CBF82C86-3A8B-457F-92A4-1AC7BF436CE9},1", |
| 3291 | 1 | 3621 | 1 |
| 3292 | ], | 3622 | ], |
| 3293 | [ | 3623 | [ |
| 3294 | "{68A1E0E1-7A39-4AB6-AD71-4D8A18878D79},1", | 3624 | "{42010094-E377-4A97-9395-AD45808D0552},1", |
| 3295 | 1 | 3625 | 1 |
| 3296 | ], | 3626 | ], |
| 3297 | [ | 3627 | [ |
| 3298 | "{7E9041BD-301B-4B5B-9456-1C92050E4914},1", | 3628 | "{0F6ABB5E-A021-4E1D-B187-7A72E74000DC},1", |
| 3299 | 1 | 3629 | 1 |
| 3300 | ], | 3630 | ], |
| 3301 | [ | 3631 | [ |
| 3302 | "{EFA77BAE-8A98-4176-B536-B615D1D2ED2B},1", | 3632 | "{F9DA3139-5856-4B11-95C0-356683E3ACFE},1", |
| 3303 | 1 | 3633 | 1 |
| 3304 | ], | 3634 | ], |
| 3305 | [ | 3635 | [ |
| 3306 | "{87AC0239-51A5-47C6-BAF4-926CCF56385C},1", | 3636 | "{439B80E0-9D4C-4A2F-9163-4E301491364D},1", |
| 3307 | 1 | 3637 | 1 |
| 3308 | ], | 3638 | ], |
| 3309 | [ | 3639 | [ |
| 3310 | "{C50BF7FC-F848-4B91-B8C8-743D0AA5F195},1", | 3640 | "{B03B973C-50BC-4B9A-9914-B1D5C0CF6197},1", |
| 3311 | 1 | 3641 | 1 |
| 3312 | ], | 3642 | ], |
| 3313 | [ | 3643 | [ |
| 3314 | "{FD8D07D0-C447-4516-A7DB-F8522B77D4A7},1", | 3644 | "{A459C474-E6EB-4BE5-9193-271799B2860B},1", |
| 3315 | 1 | 3645 | 1 |
| 3316 | ], | 3646 | ], |
| 3317 | [ | 3647 | [ |
| 3318 | "{D2E1FE57-F45D-49C3-B388-95222ACA3886},1", | 3648 | "{4ED14CEE-8560-4125-B488-9945C116741E},1", |
| 3319 | 1 | 3649 | 1 |
| 3320 | ], | 3650 | ], |
| 3321 | [ | 3651 | [ |
| 3322 | "{7F6702B3-C977-436F-82AF-403E2BE950E7},1", | 3652 | "{C4FF8FE4-4C43-461F-98DD-EB1FF2A863B2},1", |
| 3323 | 1 | 3653 | 1 |
| 3324 | ], | 3654 | ], |
| 3325 | [ | 3655 | [ |
| 3326 | "{688C4EA1-80F5-4D85-B9BB-F8633FF3C0CA},1", | 3656 | "{932BE37F-05AA-4EFB-85D8-A33445E738B3},1", |
| 3327 | 1 | 3657 | 1 |
| 3328 | ], | 3658 | ], |
| 3329 | [ | 3659 | [ |
| 3330 | "{754FA957-C432-4502-A5CF-EE44449462C0},1", | 3660 | "{9B755368-1D49-4D7E-8D33-6DC536F57848},1", |
| 3331 | 1 | 3661 | 1 |
| 3332 | ], | 3662 | ], |
| 3333 | [ | 3663 | [ |
| 3334 | "{D2197C55-D962-4DF0-8B14-26F88C06D6FA},1", | 3664 | "{D045EAAF-ACE9-4B4F-979B-E348A4ADE693},1", |
| 3335 | 1 | 3665 | 1 |
| 3336 | ], | 3666 | ], |
| 3337 | [ | 3667 | [ |
| 3338 | "{87B83BA8-A906-4F71-A6CA-3E8133A75D34},1", | 3668 | "{5CA4C59F-B400-4BC6-8B90-30ECAC35CB43},1", |
| 3339 | 1 | 3669 | 1 |
| 3340 | ], | 3670 | ], |
| 3341 | [ | 3671 | [ |
| 3342 | "{F2D6AEBB-98EA-4F10-991F-5243E74AF450},1", | 3672 | "{D63FB383-FB37-44F9-AEBB-E868E903A2BB},1", |
| 3343 | 1 | 3673 | 1 |
| 3344 | ], | 3674 | ], |
| 3345 | [ | 3675 | [ |
| 3346 | "{406687D7-F9E9-491B-85DA-166DE6CD5A60},1", | 3676 | "{C6E54364-6CD0-4764-8180-873030B9EA53},1", |
| 3347 | 1 | 3677 | 1 |
| 3348 | ], | 3678 | ], |
| 3349 | [ | 3679 | [ |
| 3350 | "{1F73BF36-9FC2-495D-844D-B370D5C47DE7},1", | 3680 | "{DEEC391B-F2D1-4EB1-BEB1-57598DFBB982},1", |
| 3351 | 1 | 3681 | 1 |
| 3352 | ], | 3682 | ], |
| 3353 | [ | 3683 | [ |
| 3354 | "{E9BD343F-4A66-4DE2-81F9-3789CCA22818},1", | 3684 | "{9E7616ED-DE19-4625-8EA0-0C5DEB18B690},1", |
| 3355 | 1 | 3685 | 1 |
| 3356 | ], | 3686 | ], |
| 3357 | [ | 3687 | [ |
| 3358 | "{189EA744-BE13-467A-95BA-2667A134C0B0},1", | 3688 | "{89F1748D-6749-4AD0-BAF6-07E9F9C058E5},1", |
| 3359 | 1 | 3689 | 1 |
| 3360 | ], | 3690 | ], |
| 3361 | [ | 3691 | [ |
| 3362 | "{017F5881-F818-4C7C-B9F9-6BCB4085F1D0},1", | 3692 | "{00B10C58-6649-428D-A081-3EBBA6E88E97},1", |
| 3363 | 1 | 3693 | 1 |
| 3364 | ], | 3694 | ], |
| 3365 | [ | 3695 | [ |
| 3366 | "{CD0D17F4-25D8-4076-9AF7-47D9CDEFCF0A},1", | 3696 | "{58E99482-710A-4938-8CFB-88A1C9B946C9},1", |
| 3367 | 1 | 3697 | 1 |
| 3368 | ], | 3698 | ], |
| 3369 | [ | 3699 | [ |
| 3370 | "{CCBB5969-BCE8-498B-BE1A-98FD3A44DB1A},1", | 3700 | "{F721C9D5-9A6A-405F-94C9-9B85A7EEB980},1", |
| 3371 | 1 | 3701 | 1 |
| 3372 | ], | 3702 | ], |
| 3373 | [ | 3703 | [ |
| 3374 | "{2B736B19-2D9C-4E76-A732-478D533D717A},1", | 3704 | "{15A45C01-A9E8-42E2-A267-C7FCE728815E},1", |
| 3375 | 1 | 3705 | 1 |
| 3376 | ] | 3706 | ] |
| 3377 | ] | 3707 | ] |
| 3378 | } | 3708 | } |
| 3379 | }, | 3709 | }, |
| 3380 | "space": "{90FCDB0C-36AE-422F-B4FE-67676826FE36},1" | 3710 | "space": "{A5A2AFBE-1DF1-4190-AAC7-1AD29C9564F2},1" |
| 3381 | }, | 3711 | }, |
| 3382 | { | 3712 | { |
| 3383 | "id": 2, | 3713 | "id": 2, |
| 3384 | "operation": { | 3714 | "operation": { |
| 3385 | "Page": { | 3715 | "Page": { |
| 3386 | "after": { | 3716 | "after": { |
| 3387 | "created": 134335976560000000, | 3717 | "created": 134348779890000000, |
| 3388 | "definitions": { | 3718 | "definitions": { |
| 3389 | "{7E9041BD-301B-4B5B-9456-1C92050E4914},18": { | 3719 | "{0F6ABB5E-A021-4E1D-B187-7A72E74000DC},18": { |
| 3390 | "format": { | 3720 | "format": { |
| 3391 | "alignment": null, | 3721 | "alignment": null, |
| 3392 | "bold": null, | 3722 | "bold": null, |
| ... | @@ -3403,6 +3733,7 @@ | ... | @@ -3403,6 +3733,7 @@ |
| 3403 | "line_spacing": null, | 3733 | "line_spacing": null, |
| 3404 | "list_spacing": null, | 3734 | "list_spacing": null, |
| 3405 | "math": null, | 3735 | "math": null, |
| 3736 | "math_object": null, | ||
| 3406 | "rtl": null, | 3737 | "rtl": null, |
| 3407 | "space_after": null, | 3738 | "space_after": null, |
| 3408 | "space_before": null, | 3739 | "space_before": null, |
| ... | @@ -3417,6 +3748,24 @@ | ... | @@ -3417,6 +3748,24 @@ |
| 3417 | } | 3748 | } |
| 3418 | } | 3749 | } |
| 3419 | }, | 3750 | }, |
| 3751 | "identity": [ | ||
| 3752 | 94, | ||
| 3753 | 187, | ||
| 3754 | 106, | ||
| 3755 | 15, | ||
| 3756 | 33, | ||
| 3757 | 160, | ||
| 3758 | 29, | ||
| 3759 | 78, | ||
| 3760 | 177, | ||
| 3761 | 135, | ||
| 3762 | 122, | ||
| 3763 | 114, | ||
| 3764 | 231, | ||
| 3765 | 64, | ||
| 3766 | 0, | ||
| 3767 | 220 | ||
| 3768 | ], | ||
| 3420 | "margin_origin": [ | 3769 | "margin_origin": [ |
| 3421 | 0.0, | 3770 | 0.0, |
| 3422 | 0.0 | 3771 | 0.0 |
| ... | @@ -3424,7 +3773,7 @@ | ... | @@ -3424,7 +3773,7 @@ |
| 3424 | "objects": [ | 3773 | "objects": [ |
| 3425 | { | 3774 | { |
| 3426 | "Outline": { | 3775 | "Outline": { |
| 3427 | "id": "{9D60419E-FC09-4547-8A45-5083DF888B11},0", | 3776 | "id": "{91379CE6-91DA-48BA-B306-77E4CEB068B6},17", |
| 3428 | "indents": [], | 3777 | "indents": [], |
| 3429 | "layout": { | 3778 | "layout": { |
| 3430 | "max_height": null, | 3779 | "max_height": null, |
| ... | @@ -3441,7 +3790,7 @@ | ... | @@ -3441,7 +3790,7 @@ |
| 3441 | "content": { | 3790 | "content": { |
| 3442 | "Text": { | 3791 | "Text": { |
| 3443 | "date_field": null, | 3792 | "date_field": null, |
| 3444 | "id": "{A30B6C43-D76B-4629-B1C4-AC8F2FF6CBEE},0", | 3793 | "id": "{91379CE6-91DA-48BA-B306-77E4CEB068B6},16", |
| 3445 | "tags": [], | 3794 | "tags": [], |
| 3446 | "text": { | 3795 | "text": { |
| 3447 | "spans": [ | 3796 | "spans": [ |
| ... | @@ -3463,6 +3812,7 @@ | ... | @@ -3463,6 +3812,7 @@ |
| 3463 | "line_spacing": null, | 3812 | "line_spacing": null, |
| 3464 | "list_spacing": null, | 3813 | "list_spacing": null, |
| 3465 | "math": null, | 3814 | "math": null, |
| 3815 | "math_object": null, | ||
| 3466 | "rtl": null, | 3816 | "rtl": null, |
| 3467 | "space_after": null, | 3817 | "space_after": null, |
| 3468 | "space_before": null, | 3818 | "space_before": null, |
| ... | @@ -3493,6 +3843,7 @@ | ... | @@ -3493,6 +3843,7 @@ |
| 3493 | "line_spacing": null, | 3843 | "line_spacing": null, |
| 3494 | "list_spacing": null, | 3844 | "list_spacing": null, |
| 3495 | "math": null, | 3845 | "math": null, |
| 3846 | "math_object": null, | ||
| 3496 | "rtl": null, | 3847 | "rtl": null, |
| 3497 | "space_after": null, | 3848 | "space_after": null, |
| 3498 | "space_before": null, | 3849 | "space_before": null, |
| ... | @@ -3501,9 +3852,13 @@ | ... | @@ -3501,9 +3852,13 @@ |
| 3501 | "superscript": null, | 3852 | "superscript": null, |
| 3502 | "underline": null | 3853 | "underline": null |
| 3503 | }, | 3854 | }, |
| 3504 | "id": "{AD6C0452-3D66-42DF-8865-A679E80B3F05},0", | 3855 | "id": "{91379CE6-91DA-48BA-B306-77E4CEB068B6},18", |
| 3505 | "level": 1, | 3856 | "level": 1, |
| 3506 | "lists": [], | 3857 | "lists": [], |
| 3858 | "media": { | ||
| 3859 | "recordings": [], | ||
| 3860 | "time_ms": null | ||
| 3861 | }, | ||
| 3507 | "parent": null, | 3862 | "parent": null, |
| 3508 | "style": null, | 3863 | "style": null, |
| 3509 | "tags": [] | 3864 | "tags": [] |
| ... | @@ -3516,7 +3871,7 @@ | ... | @@ -3516,7 +3871,7 @@ |
| 3516 | { | 3871 | { |
| 3517 | "Title": { | 3872 | "Title": { |
| 3518 | "date": null, | 3873 | "date": null, |
| 3519 | "id": "{7E9041BD-301B-4B5B-9456-1C92050E4914},13", | 3874 | "id": "{0F6ABB5E-A021-4E1D-B187-7A72E74000DC},13", |
| 3520 | "layout": { | 3875 | "layout": { |
| 3521 | "max_height": null, | 3876 | "max_height": null, |
| 3522 | "max_width": null, | 3877 | "max_width": null, |
| ... | @@ -3527,24 +3882,29 @@ | ... | @@ -3527,24 +3882,29 @@ |
| 3527 | }, | 3882 | }, |
| 3528 | "outlines": [ | 3883 | "outlines": [ |
| 3529 | { | 3884 | { |
| 3530 | "id": "{7E9041BD-301B-4B5B-9456-1C92050E4914},14", | 3885 | "id": "{0F6ABB5E-A021-4E1D-B187-7A72E74000DC},14", |
| 3531 | "indents": [], | 3886 | "indents": [ |
| 3887 | 18.0, | ||
| 3888 | 0.0, | ||
| 3889 | 27.0, | ||
| 3890 | 27.0 | ||
| 3891 | ], | ||
| 3532 | "layout": { | 3892 | "layout": { |
| 3533 | "max_height": null, | 3893 | "max_height": 21.600000381469727, |
| 3534 | "max_width": null, | 3894 | "max_width": null, |
| 3535 | "reserved_width": null, | 3895 | "reserved_width": null, |
| 3536 | "width_set_by_user": null, | 3896 | "width_set_by_user": null, |
| 3537 | "x": null, | 3897 | "x": null, |
| 3538 | "y": null | 3898 | "y": null |
| 3539 | }, | 3899 | }, |
| 3540 | "min_width": null, | 3900 | "min_width": 162.0, |
| 3541 | "paragraphs": [ | 3901 | "paragraphs": [ |
| 3542 | { | 3902 | { |
| 3543 | "collapsed": false, | 3903 | "collapsed": false, |
| 3544 | "content": { | 3904 | "content": { |
| 3545 | "Text": { | 3905 | "Text": { |
| 3546 | "date_field": null, | 3906 | "date_field": null, |
| 3547 | "id": "{7E9041BD-301B-4B5B-9456-1C92050E4914},16", | 3907 | "id": "{0F6ABB5E-A021-4E1D-B187-7A72E74000DC},16", |
| 3548 | "tags": [], | 3908 | "tags": [], |
| 3549 | "text": { | 3909 | "text": { |
| 3550 | "spans": [ | 3910 | "spans": [ |
| ... | @@ -3566,6 +3926,7 @@ | ... | @@ -3566,6 +3926,7 @@ |
| 3566 | "line_spacing": null, | 3926 | "line_spacing": null, |
| 3567 | "list_spacing": null, | 3927 | "list_spacing": null, |
| 3568 | "math": null, | 3928 | "math": null, |
| 3929 | "math_object": null, | ||
| 3569 | "rtl": null, | 3930 | "rtl": null, |
| 3570 | "space_after": null, | 3931 | "space_after": null, |
| 3571 | "space_before": null, | 3932 | "space_before": null, |
| ... | @@ -3596,6 +3957,7 @@ | ... | @@ -3596,6 +3957,7 @@ |
| 3596 | "line_spacing": null, | 3957 | "line_spacing": null, |
| 3597 | "list_spacing": null, | 3958 | "list_spacing": null, |
| 3598 | "math": null, | 3959 | "math": null, |
| 3960 | "math_object": null, | ||
| 3599 | "rtl": null, | 3961 | "rtl": null, |
| 3600 | "space_after": null, | 3962 | "space_after": null, |
| 3601 | "space_before": null, | 3963 | "space_before": null, |
| ... | @@ -3604,15 +3966,19 @@ | ... | @@ -3604,15 +3966,19 @@ |
| 3604 | "superscript": null, | 3966 | "superscript": null, |
| 3605 | "underline": null | 3967 | "underline": null |
| 3606 | }, | 3968 | }, |
| 3607 | "id": "{7E9041BD-301B-4B5B-9456-1C92050E4914},15", | 3969 | "id": "{0F6ABB5E-A021-4E1D-B187-7A72E74000DC},15", |
| 3608 | "level": 1, | 3970 | "level": 1, |
| 3609 | "lists": [], | 3971 | "lists": [], |
| 3972 | "media": { | ||
| 3973 | "recordings": [], | ||
| 3974 | "time_ms": null | ||
| 3975 | }, | ||
| 3610 | "parent": null, | 3976 | "parent": null, |
| 3611 | "style": "{7E9041BD-301B-4B5B-9456-1C92050E4914},18", | 3977 | "style": "{0F6ABB5E-A021-4E1D-B187-7A72E74000DC},18", |
| 3612 | "tags": [] | 3978 | "tags": [] |
| 3613 | } | 3979 | } |
| 3614 | ], | 3980 | ], |
| 3615 | "title": false, | 3981 | "title": true, |
| 3616 | "unsupported": [] | 3982 | "unsupported": [] |
| 3617 | } | 3983 | } |
| 3618 | ] | 3984 | ] |
| ... | @@ -3623,9 +3989,9 @@ | ... | @@ -3623,9 +3989,9 @@ |
| 3623 | }, | 3989 | }, |
| 3624 | "author": "Author", | 3990 | "author": "Author", |
| 3625 | "before": { | 3991 | "before": { |
| 3626 | "created": 134335976560000000, | 3992 | "created": 134348779890000000, |
| 3627 | "definitions": { | 3993 | "definitions": { |
| 3628 | "{7E9041BD-301B-4B5B-9456-1C92050E4914},18": { | 3994 | "{0F6ABB5E-A021-4E1D-B187-7A72E74000DC},18": { |
| 3629 | "format": { | 3995 | "format": { |
| 3630 | "alignment": null, | 3996 | "alignment": null, |
| 3631 | "bold": null, | 3997 | "bold": null, |
| ... | @@ -3642,6 +4008,7 @@ | ... | @@ -3642,6 +4008,7 @@ |
| 3642 | "line_spacing": null, | 4008 | "line_spacing": null, |
| 3643 | "list_spacing": null, | 4009 | "list_spacing": null, |
| 3644 | "math": null, | 4010 | "math": null, |
| 4011 | "math_object": null, | ||
| 3645 | "rtl": null, | 4012 | "rtl": null, |
| 3646 | "space_after": null, | 4013 | "space_after": null, |
| 3647 | "space_before": null, | 4014 | "space_before": null, |
| ... | @@ -3656,6 +4023,24 @@ | ... | @@ -3656,6 +4023,24 @@ |
| 3656 | } | 4023 | } |
| 3657 | } | 4024 | } |
| 3658 | }, | 4025 | }, |
| 4026 | "identity": [ | ||
| 4027 | 94, | ||
| 4028 | 187, | ||
| 4029 | 106, | ||
| 4030 | 15, | ||
| 4031 | 33, | ||
| 4032 | 160, | ||
| 4033 | 29, | ||
| 4034 | 78, | ||
| 4035 | 177, | ||
| 4036 | 135, | ||
| 4037 | 122, | ||
| 4038 | 114, | ||
| 4039 | 231, | ||
| 4040 | 64, | ||
| 4041 | 0, | ||
| 4042 | 220 | ||
| 4043 | ], | ||
| 3659 | "margin_origin": [ | 4044 | "margin_origin": [ |
| 3660 | 0.0, | 4045 | 0.0, |
| 3661 | 0.0 | 4046 | 0.0 |
| ... | @@ -3664,7 +4049,7 @@ | ... | @@ -3664,7 +4049,7 @@ |
| 3664 | { | 4049 | { |
| 3665 | "Title": { | 4050 | "Title": { |
| 3666 | "date": null, | 4051 | "date": null, |
| 3667 | "id": "{7E9041BD-301B-4B5B-9456-1C92050E4914},13", | 4052 | "id": "{0F6ABB5E-A021-4E1D-B187-7A72E74000DC},13", |
| 3668 | "layout": { | 4053 | "layout": { |
| 3669 | "max_height": null, | 4054 | "max_height": null, |
| 3670 | "max_width": null, | 4055 | "max_width": null, |
| ... | @@ -3675,24 +4060,29 @@ | ... | @@ -3675,24 +4060,29 @@ |
| 3675 | }, | 4060 | }, |
| 3676 | "outlines": [ | 4061 | "outlines": [ |
| 3677 | { | 4062 | { |
| 3678 | "id": "{7E9041BD-301B-4B5B-9456-1C92050E4914},14", | 4063 | "id": "{0F6ABB5E-A021-4E1D-B187-7A72E74000DC},14", |
| 3679 | "indents": [], | 4064 | "indents": [ |
| 4065 | 18.0, | ||
| 4066 | 0.0, | ||
| 4067 | 27.0, | ||
| 4068 | 27.0 | ||
| 4069 | ], | ||
| 3680 | "layout": { | 4070 | "layout": { |
| 3681 | "max_height": null, | 4071 | "max_height": 21.600000381469727, |
| 3682 | "max_width": null, | 4072 | "max_width": null, |
| 3683 | "reserved_width": null, | 4073 | "reserved_width": null, |
| 3684 | "width_set_by_user": null, | 4074 | "width_set_by_user": null, |
| 3685 | "x": null, | 4075 | "x": null, |
| 3686 | "y": null | 4076 | "y": null |
| 3687 | }, | 4077 | }, |
| 3688 | "min_width": null, | 4078 | "min_width": 162.0, |
| 3689 | "paragraphs": [ | 4079 | "paragraphs": [ |
| 3690 | { | 4080 | { |
| 3691 | "collapsed": false, | 4081 | "collapsed": false, |
| 3692 | "content": { | 4082 | "content": { |
| 3693 | "Text": { | 4083 | "Text": { |
| 3694 | "date_field": null, | 4084 | "date_field": null, |
| 3695 | "id": "{7E9041BD-301B-4B5B-9456-1C92050E4914},16", | 4085 | "id": "{0F6ABB5E-A021-4E1D-B187-7A72E74000DC},16", |
| 3696 | "tags": [], | 4086 | "tags": [], |
| 3697 | "text": { | 4087 | "text": { |
| 3698 | "spans": [ | 4088 | "spans": [ |
| ... | @@ -3714,6 +4104,7 @@ | ... | @@ -3714,6 +4104,7 @@ |
| 3714 | "line_spacing": null, | 4104 | "line_spacing": null, |
| 3715 | "list_spacing": null, | 4105 | "list_spacing": null, |
| 3716 | "math": null, | 4106 | "math": null, |
| 4107 | "math_object": null, | ||
| 3717 | "rtl": null, | 4108 | "rtl": null, |
| 3718 | "space_after": null, | 4109 | "space_after": null, |
| 3719 | "space_before": null, | 4110 | "space_before": null, |
| ... | @@ -3744,6 +4135,7 @@ | ... | @@ -3744,6 +4135,7 @@ |
| 3744 | "line_spacing": null, | 4135 | "line_spacing": null, |
| 3745 | "list_spacing": null, | 4136 | "list_spacing": null, |
| 3746 | "math": null, | 4137 | "math": null, |
| 4138 | "math_object": null, | ||
| 3747 | "rtl": null, | 4139 | "rtl": null, |
| 3748 | "space_after": null, | 4140 | "space_after": null, |
| 3749 | "space_before": null, | 4141 | "space_before": null, |
| ... | @@ -3752,15 +4144,19 @@ | ... | @@ -3752,15 +4144,19 @@ |
| 3752 | "superscript": null, | 4144 | "superscript": null, |
| 3753 | "underline": null | 4145 | "underline": null |
| 3754 | }, | 4146 | }, |
| 3755 | "id": "{7E9041BD-301B-4B5B-9456-1C92050E4914},15", | 4147 | "id": "{0F6ABB5E-A021-4E1D-B187-7A72E74000DC},15", |
| 3756 | "level": 1, | 4148 | "level": 1, |
| 3757 | "lists": [], | 4149 | "lists": [], |
| 4150 | "media": { | ||
| 4151 | "recordings": [], | ||
| 4152 | "time_ms": null | ||
| 4153 | }, | ||
| 3758 | "parent": null, | 4154 | "parent": null, |
| 3759 | "style": "{7E9041BD-301B-4B5B-9456-1C92050E4914},18", | 4155 | "style": "{0F6ABB5E-A021-4E1D-B187-7A72E74000DC},18", |
| 3760 | "tags": [] | 4156 | "tags": [] |
| 3761 | } | 4157 | } |
| 3762 | ], | 4158 | ], |
| 3763 | "title": false, | 4159 | "title": true, |
| 3764 | "unsupported": [] | 4160 | "unsupported": [] |
| 3765 | } | 4161 | } |
| 3766 | ] | 4162 | ] |
| ... | @@ -3771,7 +4167,7 @@ | ... | @@ -3771,7 +4167,7 @@ |
| 3771 | } | 4167 | } |
| 3772 | } | 4168 | } |
| 3773 | }, | 4169 | }, |
| 3774 | "space": "{7E9041BD-301B-4B5B-9456-1C92050E4914},1" | 4170 | "space": "{0F6ABB5E-A021-4E1D-B187-7A72E74000DC},1" |
| 3775 | } | 4171 | } |
| 3776 | ], | 4172 | ], |
| 3777 | [ | 4173 | [ |
| ... | @@ -3782,215 +4178,215 @@ | ... | @@ -3782,215 +4178,215 @@ |
| 3782 | "edits": [ | 4178 | "edits": [ |
| 3783 | { | 4179 | { |
| 3784 | "guid": [ | 4180 | "guid": [ |
| 3785 | 235, | 4181 | 83, |
| 3786 | 237, | 4182 | 35, |
| 3787 | 5, | 4183 | 225, |
| 3788 | 138, | 4184 | 13, |
| 3789 | 5, | 4185 | 184, |
| 3790 | 243, | 4186 | 191, |
| 3791 | 246, | 4187 | 39, |
| 3792 | 64, | 4188 | 73, |
| 3793 | 171, | 4189 | 187, |
| 3794 | 74, | 4190 | 161, |
| 3795 | 60, | 4191 | 191, |
| 3796 | 18, | 4192 | 106, |
| 3797 | 85, | 4193 | 185, |
| 3798 | 228, | 4194 | 164, |
| 3799 | 190, | 4195 | 169, |
| 3800 | 225 | 4196 | 110 |
| 3801 | ], | 4197 | ], |
| 3802 | "level": 1, | 4198 | "level": 1, |
| 3803 | "position": { | 4199 | "position": { |
| 3804 | "Before": "{87AC0239-51A5-47C6-BAF4-926CCF56385C},1" | 4200 | "Before": "{439B80E0-9D4C-4A2F-9163-4E301491364D},1" |
| 3805 | }, | 4201 | }, |
| 3806 | "space": "{C50BF7FC-F848-4B91-B8C8-743D0AA5F195},1" | 4202 | "space": "{B03B973C-50BC-4B9A-9914-B1D5C0CF6197},1" |
| 3807 | }, | 4203 | }, |
| 3808 | { | 4204 | { |
| 3809 | "guid": [ | 4205 | "guid": [ |
| 3810 | 154, | 4206 | 235, |
| 3811 | 175, | 4207 | 25, |
| 3812 | 180, | 4208 | 178, |
| 3813 | 52, | 4209 | 87, |
| 3814 | 102, | 4210 | 182, |
| 3815 | 86, | 4211 | 233, |
| 3816 | 241, | 4212 | 248, |
| 3817 | 66, | 4213 | 74, |
| 3818 | 129, | 4214 | 163, |
| 3819 | 168, | 4215 | 238, |
| 3820 | 227, | 4216 | 127, |
| 3821 | 222, | 4217 | 45, |
| 3822 | 69, | 4218 | 119, |
| 3823 | 213, | 4219 | 155, |
| 3824 | 66, | 4220 | 249, |
| 3825 | 187 | 4221 | 77 |
| 3826 | ], | 4222 | ], |
| 3827 | "level": 2, | 4223 | "level": 2, |
| 3828 | "position": "Keep", | 4224 | "position": "Keep", |
| 3829 | "space": "{FD8D07D0-C447-4516-A7DB-F8522B77D4A7},1" | 4225 | "space": "{A459C474-E6EB-4BE5-9193-271799B2860B},1" |
| 3830 | } | 4226 | } |
| 3831 | ], | 4227 | ], |
| 3832 | "observed": [ | 4228 | "observed": [ |
| 3833 | [ | 4229 | [ |
| 3834 | "{869D5929-6E5E-4EDB-9639-4C56977DD447},1", | 4230 | "{0B37E922-551A-48F8-BCAC-4AB3503B4D28},1", |
| 3835 | 1 | 4231 | 1 |
| 3836 | ], | 4232 | ], |
| 3837 | [ | 4233 | [ |
| 3838 | "{B0B53F94-0105-4C59-B65B-9473F49BFF41},1", | 4234 | "{74492C4A-164B-40E9-B297-4D3B4685347D},1", |
| 3839 | 1 | 4235 | 1 |
| 3840 | ], | 4236 | ], |
| 3841 | [ | 4237 | [ |
| 3842 | "{0E98EE82-B78F-4872-8BD9-5D9B23A817C5},1", | 4238 | "{DAFF533D-608B-4735-BA1D-2354E8CB8A69},1", |
| 3843 | 1 | 4239 | 1 |
| 3844 | ], | 4240 | ], |
| 3845 | [ | 4241 | [ |
| 3846 | "{C837A033-F055-4455-B45F-17D82016921C},1", | 4242 | "{8B766F11-2A4F-4D95-8473-D3CA68129E11},1", |
| 3847 | 1 | 4243 | 1 |
| 3848 | ], | 4244 | ], |
| 3849 | [ | 4245 | [ |
| 3850 | "{89D86121-BF5C-418B-BB92-CBFFA8EF545F},1", | 4246 | "{28E0CC5F-68BC-4A86-9ED7-114FFD3BAE9A},1", |
| 3851 | 1 | 4247 | 1 |
| 3852 | ], | 4248 | ], |
| 3853 | [ | 4249 | [ |
| 3854 | "{57E835EA-2799-4118-B55C-7BB1E51C6941},1", | 4250 | "{31FFA5F9-C1B8-412D-A2D6-7ACC3CC16FD0},1", |
| 3855 | 1 | 4251 | 1 |
| 3856 | ], | 4252 | ], |
| 3857 | [ | 4253 | [ |
| 3858 | "{F7EECCCF-B6B4-44F2-A543-36837EC18B8A},1", | 4254 | "{AFCD09F6-1085-4582-BDB2-4B91E15D2088},1", |
| 3859 | 1 | 4255 | 1 |
| 3860 | ], | 4256 | ], |
| 3861 | [ | 4257 | [ |
| 3862 | "{361A0F09-EA63-4F2D-9620-C061D4E4707E},1", | 4258 | "{A94D5882-C902-4CAE-B38E-2ED56EEF177F},1", |
| 3863 | 1 | 4259 | 1 |
| 3864 | ], | 4260 | ], |
| 3865 | [ | 4261 | [ |
| 3866 | "{EB3940F1-F255-42BE-8C31-DD09E86DBAAF},1", | 4262 | "{5AB37A0F-5702-4C8E-A8C6-79ED6ED1E69F},1", |
| 3867 | 1 | 4263 | 1 |
| 3868 | ], | 4264 | ], |
| 3869 | [ | 4265 | [ |
| 3870 | "{C2870D07-8ADC-44BB-9F12-15D6C3708689},1", | 4266 | "{F212795E-0E4B-4844-A8A6-ABD7F7025124},1", |
| 3871 | 1 | 4267 | 1 |
| 3872 | ], | 4268 | ], |
| 3873 | [ | 4269 | [ |
| 3874 | "{1E0C6331-32C9-4EF9-8087-150A8669FA91},1", | 4270 | "{1786F2CA-0A43-4081-8B55-B207EEE0331C},1", |
| 3875 | 1 | 4271 | 1 |
| 3876 | ], | 4272 | ], |
| 3877 | [ | 4273 | [ |
| 3878 | "{ED9769DD-99D6-4BE2-B980-580DA9C4DFCE},1", | 4274 | "{3E966EF8-B7B4-483F-B241-8F21DF318AB3},1", |
| 3879 | 1 | 4275 | 1 |
| 3880 | ], | 4276 | ], |
| 3881 | [ | 4277 | [ |
| 3882 | "{17C30054-7129-4A10-9D00-4D5545E4F4BA},1", | 4278 | "{0D361C76-92FD-4B76-901B-809C2C3C3CE6},1", |
| 3883 | 1 | 4279 | 1 |
| 3884 | ], | 4280 | ], |
| 3885 | [ | 4281 | [ |
| 3886 | "{4C5FBA5B-E348-4750-ACE4-E9694D527BFF},1", | 4282 | "{AE89305A-73F4-46B5-B41D-18884685D896},1", |
| 3887 | 1 | 4283 | 1 |
| 3888 | ], | 4284 | ], |
| 3889 | [ | 4285 | [ |
| 3890 | "{BD0EF658-4D0F-42A7-B546-B4F4A77D246B},1", | 4286 | "{551D2637-2AA7-4923-B2DD-31C2E1489FD2},1", |
| 3891 | 1 | 4287 | 1 |
| 3892 | ], | 4288 | ], |
| 3893 | [ | 4289 | [ |
| 3894 | "{6FEA70FA-1451-4A27-8A8E-C4CC3B713CF6},1", | 4290 | "{CBF82C86-3A8B-457F-92A4-1AC7BF436CE9},1", |
| 3895 | 1 | 4291 | 1 |
| 3896 | ], | 4292 | ], |
| 3897 | [ | 4293 | [ |
| 3898 | "{68A1E0E1-7A39-4AB6-AD71-4D8A18878D79},1", | 4294 | "{42010094-E377-4A97-9395-AD45808D0552},1", |
| 3899 | 1 | 4295 | 1 |
| 3900 | ], | 4296 | ], |
| 3901 | [ | 4297 | [ |
| 3902 | "{7E9041BD-301B-4B5B-9456-1C92050E4914},1", | 4298 | "{0F6ABB5E-A021-4E1D-B187-7A72E74000DC},1", |
| 3903 | 1 | 4299 | 1 |
| 3904 | ], | 4300 | ], |
| 3905 | [ | 4301 | [ |
| 3906 | "{EFA77BAE-8A98-4176-B536-B615D1D2ED2B},1", | 4302 | "{F9DA3139-5856-4B11-95C0-356683E3ACFE},1", |
| 3907 | 1 | 4303 | 1 |
| 3908 | ], | 4304 | ], |
| 3909 | [ | 4305 | [ |
| 3910 | "{87AC0239-51A5-47C6-BAF4-926CCF56385C},1", | 4306 | "{439B80E0-9D4C-4A2F-9163-4E301491364D},1", |
| 3911 | 1 | 4307 | 1 |
| 3912 | ], | 4308 | ], |
| 3913 | [ | 4309 | [ |
| 3914 | "{C50BF7FC-F848-4B91-B8C8-743D0AA5F195},1", | 4310 | "{B03B973C-50BC-4B9A-9914-B1D5C0CF6197},1", |
| 3915 | 1 | 4311 | 1 |
| 3916 | ], | 4312 | ], |
| 3917 | [ | 4313 | [ |
| 3918 | "{FD8D07D0-C447-4516-A7DB-F8522B77D4A7},1", | 4314 | "{A459C474-E6EB-4BE5-9193-271799B2860B},1", |
| 3919 | 1 | 4315 | 1 |
| 3920 | ], | 4316 | ], |
| 3921 | [ | 4317 | [ |
| 3922 | "{D2E1FE57-F45D-49C3-B388-95222ACA3886},1", | 4318 | "{4ED14CEE-8560-4125-B488-9945C116741E},1", |
| 3923 | 1 | 4319 | 1 |
| 3924 | ], | 4320 | ], |
| 3925 | [ | 4321 | [ |
| 3926 | "{7F6702B3-C977-436F-82AF-403E2BE950E7},1", | 4322 | "{C4FF8FE4-4C43-461F-98DD-EB1FF2A863B2},1", |
| 3927 | 1 | 4323 | 1 |
| 3928 | ], | 4324 | ], |
| 3929 | [ | 4325 | [ |
| 3930 | "{688C4EA1-80F5-4D85-B9BB-F8633FF3C0CA},1", | 4326 | "{932BE37F-05AA-4EFB-85D8-A33445E738B3},1", |
| 3931 | 1 | 4327 | 1 |
| 3932 | ], | 4328 | ], |
| 3933 | [ | 4329 | [ |
| 3934 | "{754FA957-C432-4502-A5CF-EE44449462C0},1", | 4330 | "{9B755368-1D49-4D7E-8D33-6DC536F57848},1", |
| 3935 | 1 | 4331 | 1 |
| 3936 | ], | 4332 | ], |
| 3937 | [ | 4333 | [ |
| 3938 | "{D2197C55-D962-4DF0-8B14-26F88C06D6FA},1", | 4334 | "{D045EAAF-ACE9-4B4F-979B-E348A4ADE693},1", |
| 3939 | 1 | 4335 | 1 |
| 3940 | ], | 4336 | ], |
| 3941 | [ | 4337 | [ |
| 3942 | "{87B83BA8-A906-4F71-A6CA-3E8133A75D34},1", | 4338 | "{5CA4C59F-B400-4BC6-8B90-30ECAC35CB43},1", |
| 3943 | 1 | 4339 | 1 |
| 3944 | ], | 4340 | ], |
| 3945 | [ | 4341 | [ |
| 3946 | "{F2D6AEBB-98EA-4F10-991F-5243E74AF450},1", | 4342 | "{D63FB383-FB37-44F9-AEBB-E868E903A2BB},1", |
| 3947 | 1 | 4343 | 1 |
| 3948 | ], | 4344 | ], |
| 3949 | [ | 4345 | [ |
| 3950 | "{406687D7-F9E9-491B-85DA-166DE6CD5A60},1", | 4346 | "{C6E54364-6CD0-4764-8180-873030B9EA53},1", |
| 3951 | 1 | 4347 | 1 |
| 3952 | ], | 4348 | ], |
| 3953 | [ | 4349 | [ |
| 3954 | "{1F73BF36-9FC2-495D-844D-B370D5C47DE7},1", | 4350 | "{DEEC391B-F2D1-4EB1-BEB1-57598DFBB982},1", |
| 3955 | 1 | 4351 | 1 |
| 3956 | ], | 4352 | ], |
| 3957 | [ | 4353 | [ |
| 3958 | "{E9BD343F-4A66-4DE2-81F9-3789CCA22818},1", | 4354 | "{9E7616ED-DE19-4625-8EA0-0C5DEB18B690},1", |
| 3959 | 1 | 4355 | 1 |
| 3960 | ], | 4356 | ], |
| 3961 | [ | 4357 | [ |
| 3962 | "{189EA744-BE13-467A-95BA-2667A134C0B0},1", | 4358 | "{89F1748D-6749-4AD0-BAF6-07E9F9C058E5},1", |
| 3963 | 1 | 4359 | 1 |
| 3964 | ], | 4360 | ], |
| 3965 | [ | 4361 | [ |
| 3966 | "{017F5881-F818-4C7C-B9F9-6BCB4085F1D0},1", | 4362 | "{00B10C58-6649-428D-A081-3EBBA6E88E97},1", |
| 3967 | 1 | 4363 | 1 |
| 3968 | ], | 4364 | ], |
| 3969 | [ | 4365 | [ |
| 3970 | "{CD0D17F4-25D8-4076-9AF7-47D9CDEFCF0A},1", | 4366 | "{58E99482-710A-4938-8CFB-88A1C9B946C9},1", |
| 3971 | 1 | 4367 | 1 |
| 3972 | ], | 4368 | ], |
| 3973 | [ | 4369 | [ |
| 3974 | "{CCBB5969-BCE8-498B-BE1A-98FD3A44DB1A},1", | 4370 | "{F721C9D5-9A6A-405F-94C9-9B85A7EEB980},1", |
| 3975 | 1 | 4371 | 1 |
| 3976 | ], | 4372 | ], |
| 3977 | [ | 4373 | [ |
| 3978 | "{2B736B19-2D9C-4E76-A732-478D533D717A},1", | 4374 | "{15A45C01-A9E8-42E2-A267-C7FCE728815E},1", |
| 3979 | 1 | 4375 | 1 |
| 3980 | ] | 4376 | ] |
| 3981 | ] | 4377 | ] |
| 3982 | } | 4378 | } |
| 3983 | }, | 4379 | }, |
| 3984 | "space": "{90FCDB0C-36AE-422F-B4FE-67676826FE36},1" | 4380 | "space": "{A5A2AFBE-1DF1-4190-AAC7-1AD29C9564F2},1" |
| 3985 | }, | 4381 | }, |
| 3986 | { | 4382 | { |
| 3987 | "id": 2, | 4383 | "id": 2, |
| 3988 | "operation": { | 4384 | "operation": { |
| 3989 | "Page": { | 4385 | "Page": { |
| 3990 | "after": { | 4386 | "after": { |
| 3991 | "created": 134335976560000000, | 4387 | "created": 134348779890000000, |
| 3992 | "definitions": { | 4388 | "definitions": { |
| 3993 | "{C50BF7FC-F848-4B91-B8C8-743D0AA5F195},18": { | 4389 | "{B03B973C-50BC-4B9A-9914-B1D5C0CF6197},18": { |
| 3994 | "format": { | 4390 | "format": { |
| 3995 | "alignment": null, | 4391 | "alignment": null, |
| 3996 | "bold": null, | 4392 | "bold": null, |
| ... | @@ -4007,6 +4403,7 @@ | ... | @@ -4007,6 +4403,7 @@ |
| 4007 | "line_spacing": null, | 4403 | "line_spacing": null, |
| 4008 | "list_spacing": null, | 4404 | "list_spacing": null, |
| 4009 | "math": null, | 4405 | "math": null, |
| 4406 | "math_object": null, | ||
| 4010 | "rtl": null, | 4407 | "rtl": null, |
| 4011 | "space_after": null, | 4408 | "space_after": null, |
| 4012 | "space_before": null, | 4409 | "space_before": null, |
| ... | @@ -4021,6 +4418,24 @@ | ... | @@ -4021,6 +4418,24 @@ |
| 4021 | } | 4418 | } |
| 4022 | } | 4419 | } |
| 4023 | }, | 4420 | }, |
| 4421 | "identity": [ | ||
| 4422 | 60, | ||
| 4423 | 151, | ||
| 4424 | 59, | ||
| 4425 | 176, | ||
| 4426 | 188, | ||
| 4427 | 80, | ||
| 4428 | 154, | ||
| 4429 | 75, | ||
| 4430 | 153, | ||
| 4431 | 20, | ||
| 4432 | 177, | ||
| 4433 | 213, | ||
| 4434 | 192, | ||
| 4435 | 207, | ||
| 4436 | 97, | ||
| 4437 | 151 | ||
| 4438 | ], | ||
| 4024 | "margin_origin": [ | 4439 | "margin_origin": [ |
| 4025 | 0.0, | 4440 | 0.0, |
| 4026 | 0.0 | 4441 | 0.0 |
| ... | @@ -4028,7 +4443,7 @@ | ... | @@ -4028,7 +4443,7 @@ |
| 4028 | "objects": [ | 4443 | "objects": [ |
| 4029 | { | 4444 | { |
| 4030 | "Outline": { | 4445 | "Outline": { |
| 4031 | "id": "{69602664-892F-4537-9BFE-860DAD71A595},0", | 4446 | "id": "{91379CE6-91DA-48BA-B306-77E4CEB068B6},20", |
| 4032 | "indents": [], | 4447 | "indents": [], |
| 4033 | "layout": { | 4448 | "layout": { |
| 4034 | "max_height": null, | 4449 | "max_height": null, |
| ... | @@ -4045,7 +4460,7 @@ | ... | @@ -4045,7 +4460,7 @@ |
| 4045 | "content": { | 4460 | "content": { |
| 4046 | "Text": { | 4461 | "Text": { |
| 4047 | "date_field": null, | 4462 | "date_field": null, |
| 4048 | "id": "{C533D56B-11C4-4867-B1E2-3E7520E7939A},0", | 4463 | "id": "{91379CE6-91DA-48BA-B306-77E4CEB068B6},19", |
| 4049 | "tags": [], | 4464 | "tags": [], |
| 4050 | "text": { | 4465 | "text": { |
| 4051 | "spans": [ | 4466 | "spans": [ |
| ... | @@ -4067,6 +4482,7 @@ | ... | @@ -4067,6 +4482,7 @@ |
| 4067 | "line_spacing": null, | 4482 | "line_spacing": null, |
| 4068 | "list_spacing": null, | 4483 | "list_spacing": null, |
| 4069 | "math": null, | 4484 | "math": null, |
| 4485 | "math_object": null, | ||
| 4070 | "rtl": null, | 4486 | "rtl": null, |
| 4071 | "space_after": null, | 4487 | "space_after": null, |
| 4072 | "space_before": null, | 4488 | "space_before": null, |
| ... | @@ -4097,6 +4513,7 @@ | ... | @@ -4097,6 +4513,7 @@ |
| 4097 | "line_spacing": null, | 4513 | "line_spacing": null, |
| 4098 | "list_spacing": null, | 4514 | "list_spacing": null, |
| 4099 | "math": null, | 4515 | "math": null, |
| 4516 | "math_object": null, | ||
| 4100 | "rtl": null, | 4517 | "rtl": null, |
| 4101 | "space_after": null, | 4518 | "space_after": null, |
| 4102 | "space_before": null, | 4519 | "space_before": null, |
| ... | @@ -4105,9 +4522,13 @@ | ... | @@ -4105,9 +4522,13 @@ |
| 4105 | "superscript": null, | 4522 | "superscript": null, |
| 4106 | "underline": null | 4523 | "underline": null |
| 4107 | }, | 4524 | }, |
| 4108 | "id": "{FB8451BB-46B9-44F1-972F-A543F55F06B5},0", | 4525 | "id": "{91379CE6-91DA-48BA-B306-77E4CEB068B6},21", |
| 4109 | "level": 1, | 4526 | "level": 1, |
| 4110 | "lists": [], | 4527 | "lists": [], |
| 4528 | "media": { | ||
| 4529 | "recordings": [], | ||
| 4530 | "time_ms": null | ||
| 4531 | }, | ||
| 4111 | "parent": null, | 4532 | "parent": null, |
| 4112 | "style": null, | 4533 | "style": null, |
| 4113 | "tags": [] | 4534 | "tags": [] |
| ... | @@ -4120,7 +4541,7 @@ | ... | @@ -4120,7 +4541,7 @@ |
| 4120 | { | 4541 | { |
| 4121 | "Title": { | 4542 | "Title": { |
| 4122 | "date": null, | 4543 | "date": null, |
| 4123 | "id": "{C50BF7FC-F848-4B91-B8C8-743D0AA5F195},13", | 4544 | "id": "{B03B973C-50BC-4B9A-9914-B1D5C0CF6197},13", |
| 4124 | "layout": { | 4545 | "layout": { |
| 4125 | "max_height": null, | 4546 | "max_height": null, |
| 4126 | "max_width": null, | 4547 | "max_width": null, |
| ... | @@ -4131,24 +4552,29 @@ | ... | @@ -4131,24 +4552,29 @@ |
| 4131 | }, | 4552 | }, |
| 4132 | "outlines": [ | 4553 | "outlines": [ |
| 4133 | { | 4554 | { |
| 4134 | "id": "{C50BF7FC-F848-4B91-B8C8-743D0AA5F195},14", | 4555 | "id": "{B03B973C-50BC-4B9A-9914-B1D5C0CF6197},14", |
| 4135 | "indents": [], | 4556 | "indents": [ |
| 4557 | 18.0, | ||
| 4558 | 0.0, | ||
| 4559 | 27.0, | ||
| 4560 | 27.0 | ||
| 4561 | ], | ||
| 4136 | "layout": { | 4562 | "layout": { |
| 4137 | "max_height": null, | 4563 | "max_height": 21.600000381469727, |
| 4138 | "max_width": null, | 4564 | "max_width": null, |
| 4139 | "reserved_width": null, | 4565 | "reserved_width": null, |
| 4140 | "width_set_by_user": null, | 4566 | "width_set_by_user": null, |
| 4141 | "x": null, | 4567 | "x": null, |
| 4142 | "y": null | 4568 | "y": null |
| 4143 | }, | 4569 | }, |
| 4144 | "min_width": null, | 4570 | "min_width": 162.0, |
| 4145 | "paragraphs": [ | 4571 | "paragraphs": [ |
| 4146 | { | 4572 | { |
| 4147 | "collapsed": false, | 4573 | "collapsed": false, |
| 4148 | "content": { | 4574 | "content": { |
| 4149 | "Text": { | 4575 | "Text": { |
| 4150 | "date_field": null, | 4576 | "date_field": null, |
| 4151 | "id": "{C50BF7FC-F848-4B91-B8C8-743D0AA5F195},16", | 4577 | "id": "{B03B973C-50BC-4B9A-9914-B1D5C0CF6197},16", |
| 4152 | "tags": [], | 4578 | "tags": [], |
| 4153 | "text": { | 4579 | "text": { |
| 4154 | "spans": [ | 4580 | "spans": [ |
| ... | @@ -4170,6 +4596,7 @@ | ... | @@ -4170,6 +4596,7 @@ |
| 4170 | "line_spacing": null, | 4596 | "line_spacing": null, |
| 4171 | "list_spacing": null, | 4597 | "list_spacing": null, |
| 4172 | "math": null, | 4598 | "math": null, |
| 4599 | "math_object": null, | ||
| 4173 | "rtl": null, | 4600 | "rtl": null, |
| 4174 | "space_after": null, | 4601 | "space_after": null, |
| 4175 | "space_before": null, | 4602 | "space_before": null, |
| ... | @@ -4200,6 +4627,7 @@ | ... | @@ -4200,6 +4627,7 @@ |
| 4200 | "line_spacing": null, | 4627 | "line_spacing": null, |
| 4201 | "list_spacing": null, | 4628 | "list_spacing": null, |
| 4202 | "math": null, | 4629 | "math": null, |
| 4630 | "math_object": null, | ||
| 4203 | "rtl": null, | 4631 | "rtl": null, |
| 4204 | "space_after": null, | 4632 | "space_after": null, |
| 4205 | "space_before": null, | 4633 | "space_before": null, |
| ... | @@ -4208,15 +4636,19 @@ | ... | @@ -4208,15 +4636,19 @@ |
| 4208 | "superscript": null, | 4636 | "superscript": null, |
| 4209 | "underline": null | 4637 | "underline": null |
| 4210 | }, | 4638 | }, |
| 4211 | "id": "{C50BF7FC-F848-4B91-B8C8-743D0AA5F195},15", | 4639 | "id": "{B03B973C-50BC-4B9A-9914-B1D5C0CF6197},15", |
| 4212 | "level": 1, | 4640 | "level": 1, |
| 4213 | "lists": [], | 4641 | "lists": [], |
| 4642 | "media": { | ||
| 4643 | "recordings": [], | ||
| 4644 | "time_ms": null | ||
| 4645 | }, | ||
| 4214 | "parent": null, | 4646 | "parent": null, |
| 4215 | "style": "{C50BF7FC-F848-4B91-B8C8-743D0AA5F195},18", | 4647 | "style": "{B03B973C-50BC-4B9A-9914-B1D5C0CF6197},18", |
| 4216 | "tags": [] | 4648 | "tags": [] |
| 4217 | } | 4649 | } |
| 4218 | ], | 4650 | ], |
| 4219 | "title": false, | 4651 | "title": true, |
| 4220 | "unsupported": [] | 4652 | "unsupported": [] |
| 4221 | } | 4653 | } |
| 4222 | ] | 4654 | ] |
| ... | @@ -4227,9 +4659,9 @@ | ... | @@ -4227,9 +4659,9 @@ |
| 4227 | }, | 4659 | }, |
| 4228 | "author": "Author", | 4660 | "author": "Author", |
| 4229 | "before": { | 4661 | "before": { |
| 4230 | "created": 134335976560000000, | 4662 | "created": 134348779890000000, |
| 4231 | "definitions": { | 4663 | "definitions": { |
| 4232 | "{C50BF7FC-F848-4B91-B8C8-743D0AA5F195},18": { | 4664 | "{B03B973C-50BC-4B9A-9914-B1D5C0CF6197},18": { |
| 4233 | "format": { | 4665 | "format": { |
| 4234 | "alignment": null, | 4666 | "alignment": null, |
| 4235 | "bold": null, | 4667 | "bold": null, |
| ... | @@ -4246,6 +4678,7 @@ | ... | @@ -4246,6 +4678,7 @@ |
| 4246 | "line_spacing": null, | 4678 | "line_spacing": null, |
| 4247 | "list_spacing": null, | 4679 | "list_spacing": null, |
| 4248 | "math": null, | 4680 | "math": null, |
| 4681 | "math_object": null, | ||
| 4249 | "rtl": null, | 4682 | "rtl": null, |
| 4250 | "space_after": null, | 4683 | "space_after": null, |
| 4251 | "space_before": null, | 4684 | "space_before": null, |
| ... | @@ -4260,6 +4693,24 @@ | ... | @@ -4260,6 +4693,24 @@ |
| 4260 | } | 4693 | } |
| 4261 | } | 4694 | } |
| 4262 | }, | 4695 | }, |
| 4696 | "identity": [ | ||
| 4697 | 60, | ||
| 4698 | 151, | ||
| 4699 | 59, | ||
| 4700 | 176, | ||
| 4701 | 188, | ||
| 4702 | 80, | ||
| 4703 | 154, | ||
| 4704 | 75, | ||
| 4705 | 153, | ||
| 4706 | 20, | ||
| 4707 | 177, | ||
| 4708 | 213, | ||
| 4709 | 192, | ||
| 4710 | 207, | ||
| 4711 | 97, | ||
| 4712 | 151 | ||
| 4713 | ], | ||
| 4263 | "margin_origin": [ | 4714 | "margin_origin": [ |
| 4264 | 0.0, | 4715 | 0.0, |
| 4265 | 0.0 | 4716 | 0.0 |
| ... | @@ -4268,7 +4719,7 @@ | ... | @@ -4268,7 +4719,7 @@ |
| 4268 | { | 4719 | { |
| 4269 | "Title": { | 4720 | "Title": { |
| 4270 | "date": null, | 4721 | "date": null, |
| 4271 | "id": "{C50BF7FC-F848-4B91-B8C8-743D0AA5F195},13", | 4722 | "id": "{B03B973C-50BC-4B9A-9914-B1D5C0CF6197},13", |
| 4272 | "layout": { | 4723 | "layout": { |
| 4273 | "max_height": null, | 4724 | "max_height": null, |
| 4274 | "max_width": null, | 4725 | "max_width": null, |
| ... | @@ -4279,24 +4730,29 @@ | ... | @@ -4279,24 +4730,29 @@ |
| 4279 | }, | 4730 | }, |
| 4280 | "outlines": [ | 4731 | "outlines": [ |
| 4281 | { | 4732 | { |
| 4282 | "id": "{C50BF7FC-F848-4B91-B8C8-743D0AA5F195},14", | 4733 | "id": "{B03B973C-50BC-4B9A-9914-B1D5C0CF6197},14", |
| 4283 | "indents": [], | 4734 | "indents": [ |
| 4735 | 18.0, | ||
| 4736 | 0.0, | ||
| 4737 | 27.0, | ||
| 4738 | 27.0 | ||
| 4739 | ], | ||
| 4284 | "layout": { | 4740 | "layout": { |
| 4285 | "max_height": null, | 4741 | "max_height": 21.600000381469727, |
| 4286 | "max_width": null, | 4742 | "max_width": null, |
| 4287 | "reserved_width": null, | 4743 | "reserved_width": null, |
| 4288 | "width_set_by_user": null, | 4744 | "width_set_by_user": null, |
| 4289 | "x": null, | 4745 | "x": null, |
| 4290 | "y": null | 4746 | "y": null |
| 4291 | }, | 4747 | }, |
| 4292 | "min_width": null, | 4748 | "min_width": 162.0, |
| 4293 | "paragraphs": [ | 4749 | "paragraphs": [ |
| 4294 | { | 4750 | { |
| 4295 | "collapsed": false, | 4751 | "collapsed": false, |
| 4296 | "content": { | 4752 | "content": { |
| 4297 | "Text": { | 4753 | "Text": { |
| 4298 | "date_field": null, | 4754 | "date_field": null, |
| 4299 | "id": "{C50BF7FC-F848-4B91-B8C8-743D0AA5F195},16", | 4755 | "id": "{B03B973C-50BC-4B9A-9914-B1D5C0CF6197},16", |
| 4300 | "tags": [], | 4756 | "tags": [], |
| 4301 | "text": { | 4757 | "text": { |
| 4302 | "spans": [ | 4758 | "spans": [ |
| ... | @@ -4318,6 +4774,7 @@ | ... | @@ -4318,6 +4774,7 @@ |
| 4318 | "line_spacing": null, | 4774 | "line_spacing": null, |
| 4319 | "list_spacing": null, | 4775 | "list_spacing": null, |
| 4320 | "math": null, | 4776 | "math": null, |
| 4777 | "math_object": null, | ||
| 4321 | "rtl": null, | 4778 | "rtl": null, |
| 4322 | "space_after": null, | 4779 | "space_after": null, |
| 4323 | "space_before": null, | 4780 | "space_before": null, |
| ... | @@ -4348,6 +4805,7 @@ | ... | @@ -4348,6 +4805,7 @@ |
| 4348 | "line_spacing": null, | 4805 | "line_spacing": null, |
| 4349 | "list_spacing": null, | 4806 | "list_spacing": null, |
| 4350 | "math": null, | 4807 | "math": null, |
| 4808 | "math_object": null, | ||
| 4351 | "rtl": null, | 4809 | "rtl": null, |
| 4352 | "space_after": null, | 4810 | "space_after": null, |
| 4353 | "space_before": null, | 4811 | "space_before": null, |
| ... | @@ -4356,15 +4814,19 @@ | ... | @@ -4356,15 +4814,19 @@ |
| 4356 | "superscript": null, | 4814 | "superscript": null, |
| 4357 | "underline": null | 4815 | "underline": null |
| 4358 | }, | 4816 | }, |
| 4359 | "id": "{C50BF7FC-F848-4B91-B8C8-743D0AA5F195},15", | 4817 | "id": "{B03B973C-50BC-4B9A-9914-B1D5C0CF6197},15", |
| 4360 | "level": 1, | 4818 | "level": 1, |
| 4361 | "lists": [], | 4819 | "lists": [], |
| 4820 | "media": { | ||
| 4821 | "recordings": [], | ||
| 4822 | "time_ms": null | ||
| 4823 | }, | ||
| 4362 | "parent": null, | 4824 | "parent": null, |
| 4363 | "style": "{C50BF7FC-F848-4B91-B8C8-743D0AA5F195},18", | 4825 | "style": "{B03B973C-50BC-4B9A-9914-B1D5C0CF6197},18", |
| 4364 | "tags": [] | 4826 | "tags": [] |
| 4365 | } | 4827 | } |
| 4366 | ], | 4828 | ], |
| 4367 | "title": false, | 4829 | "title": true, |
| 4368 | "unsupported": [] | 4830 | "unsupported": [] |
| 4369 | } | 4831 | } |
| 4370 | ] | 4832 | ] |
| ... | @@ -4375,7 +4837,7 @@ | ... | @@ -4375,7 +4837,7 @@ |
| 4375 | } | 4837 | } |
| 4376 | } | 4838 | } |
| 4377 | }, | 4839 | }, |
| 4378 | "space": "{C50BF7FC-F848-4B91-B8C8-743D0AA5F195},1" | 4840 | "space": "{B03B973C-50BC-4B9A-9914-B1D5C0CF6197},1" |
| 4379 | } | 4841 | } |
| 4380 | ], | 4842 | ], |
| 4381 | [ | 4843 | [ |
| ... | @@ -4386,215 +4848,215 @@ | ... | @@ -4386,215 +4848,215 @@ |
| 4386 | "edits": [ | 4848 | "edits": [ |
| 4387 | { | 4849 | { |
| 4388 | "guid": [ | 4850 | "guid": [ |
| 4389 | 133, | 4851 | 73, |
| 4390 | 13, | 4852 | 224, |
| 4391 | 22, | 4853 | 214, |
| 4392 | 96, | 4854 | 216, |
| 4393 | 19, | 4855 | 142, |
| 4394 | 247, | 4856 | 197, |
| 4395 | 50, | 4857 | 81, |
| 4396 | 69, | 4858 | 64, |
| 4397 | 147, | 4859 | 131, |
| 4398 | 11, | 4860 | 8, |
| 4399 | 146, | 4861 | 236, |
| 4400 | 207, | 4862 | 81, |
| 4401 | 6, | 4863 | 238, |
| 4402 | 153, | 4864 | 204, |
| 4403 | 24, | 4865 | 152, |
| 4404 | 94 | 4866 | 204 |
| 4405 | ], | 4867 | ], |
| 4406 | "level": 1, | 4868 | "level": 1, |
| 4407 | "position": { | 4869 | "position": { |
| 4408 | "Before": "{D2E1FE57-F45D-49C3-B388-95222ACA3886},1" | 4870 | "Before": "{4ED14CEE-8560-4125-B488-9945C116741E},1" |
| 4409 | }, | 4871 | }, |
| 4410 | "space": "{7F6702B3-C977-436F-82AF-403E2BE950E7},1" | 4872 | "space": "{C4FF8FE4-4C43-461F-98DD-EB1FF2A863B2},1" |
| 4411 | }, | 4873 | }, |
| 4412 | { | 4874 | { |
| 4413 | "guid": [ | 4875 | "guid": [ |
| 4414 | 159, | 4876 | 187, |
| 4415 | 110, | 4877 | 19, |
| 4416 | 141, | 4878 | 240, |
| 4417 | 39, | 4879 | 247, |
| 4418 | 253, | 4880 | 140, |
| 4419 | 156, | 4881 | 21, |
| 4420 | 37, | 4882 | 1, |
| 4421 | 64, | 4883 | 78, |
| 4422 | 172, | 4884 | 181, |
| 4423 | 95, | 4885 | 106, |
| 4886 | 58, | ||
| 4424 | 144, | 4887 | 144, |
| 4425 | 82, | 4888 | 123, |
| 4426 | 194, | 4889 | 115, |
| 4427 | 61, | 4890 | 155, |
| 4428 | 40, | 4891 | 206 |
| 4429 | 198 | ||
| 4430 | ], | 4892 | ], |
| 4431 | "level": 2, | 4893 | "level": 2, |
| 4432 | "position": "Keep", | 4894 | "position": "Keep", |
| 4433 | "space": "{688C4EA1-80F5-4D85-B9BB-F8633FF3C0CA},1" | 4895 | "space": "{932BE37F-05AA-4EFB-85D8-A33445E738B3},1" |
| 4434 | } | 4896 | } |
| 4435 | ], | 4897 | ], |
| 4436 | "observed": [ | 4898 | "observed": [ |
| 4437 | [ | 4899 | [ |
| 4438 | "{869D5929-6E5E-4EDB-9639-4C56977DD447},1", | 4900 | "{0B37E922-551A-48F8-BCAC-4AB3503B4D28},1", |
| 4439 | 1 | 4901 | 1 |
| 4440 | ], | 4902 | ], |
| 4441 | [ | 4903 | [ |
| 4442 | "{B0B53F94-0105-4C59-B65B-9473F49BFF41},1", | 4904 | "{74492C4A-164B-40E9-B297-4D3B4685347D},1", |
| 4443 | 1 | 4905 | 1 |
| 4444 | ], | 4906 | ], |
| 4445 | [ | 4907 | [ |
| 4446 | "{0E98EE82-B78F-4872-8BD9-5D9B23A817C5},1", | 4908 | "{DAFF533D-608B-4735-BA1D-2354E8CB8A69},1", |
| 4447 | 1 | 4909 | 1 |
| 4448 | ], | 4910 | ], |
| 4449 | [ | 4911 | [ |
| 4450 | "{C837A033-F055-4455-B45F-17D82016921C},1", | 4912 | "{8B766F11-2A4F-4D95-8473-D3CA68129E11},1", |
| 4451 | 1 | 4913 | 1 |
| 4452 | ], | 4914 | ], |
| 4453 | [ | 4915 | [ |
| 4454 | "{89D86121-BF5C-418B-BB92-CBFFA8EF545F},1", | 4916 | "{28E0CC5F-68BC-4A86-9ED7-114FFD3BAE9A},1", |
| 4455 | 1 | 4917 | 1 |
| 4456 | ], | 4918 | ], |
| 4457 | [ | 4919 | [ |
| 4458 | "{57E835EA-2799-4118-B55C-7BB1E51C6941},1", | 4920 | "{31FFA5F9-C1B8-412D-A2D6-7ACC3CC16FD0},1", |
| 4459 | 1 | 4921 | 1 |
| 4460 | ], | 4922 | ], |
| 4461 | [ | 4923 | [ |
| 4462 | "{F7EECCCF-B6B4-44F2-A543-36837EC18B8A},1", | 4924 | "{AFCD09F6-1085-4582-BDB2-4B91E15D2088},1", |
| 4463 | 1 | 4925 | 1 |
| 4464 | ], | 4926 | ], |
| 4465 | [ | 4927 | [ |
| 4466 | "{361A0F09-EA63-4F2D-9620-C061D4E4707E},1", | 4928 | "{A94D5882-C902-4CAE-B38E-2ED56EEF177F},1", |
| 4467 | 1 | 4929 | 1 |
| 4468 | ], | 4930 | ], |
| 4469 | [ | 4931 | [ |
| 4470 | "{EB3940F1-F255-42BE-8C31-DD09E86DBAAF},1", | 4932 | "{5AB37A0F-5702-4C8E-A8C6-79ED6ED1E69F},1", |
| 4471 | 1 | 4933 | 1 |
| 4472 | ], | 4934 | ], |
| 4473 | [ | 4935 | [ |
| 4474 | "{C2870D07-8ADC-44BB-9F12-15D6C3708689},1", | 4936 | "{F212795E-0E4B-4844-A8A6-ABD7F7025124},1", |
| 4475 | 1 | 4937 | 1 |
| 4476 | ], | 4938 | ], |
| 4477 | [ | 4939 | [ |
| 4478 | "{1E0C6331-32C9-4EF9-8087-150A8669FA91},1", | 4940 | "{1786F2CA-0A43-4081-8B55-B207EEE0331C},1", |
| 4479 | 1 | 4941 | 1 |
| 4480 | ], | 4942 | ], |
| 4481 | [ | 4943 | [ |
| 4482 | "{ED9769DD-99D6-4BE2-B980-580DA9C4DFCE},1", | 4944 | "{3E966EF8-B7B4-483F-B241-8F21DF318AB3},1", |
| 4483 | 1 | 4945 | 1 |
| 4484 | ], | 4946 | ], |
| 4485 | [ | 4947 | [ |
| 4486 | "{17C30054-7129-4A10-9D00-4D5545E4F4BA},1", | 4948 | "{0D361C76-92FD-4B76-901B-809C2C3C3CE6},1", |
| 4487 | 1 | 4949 | 1 |
| 4488 | ], | 4950 | ], |
| 4489 | [ | 4951 | [ |
| 4490 | "{4C5FBA5B-E348-4750-ACE4-E9694D527BFF},1", | 4952 | "{AE89305A-73F4-46B5-B41D-18884685D896},1", |
| 4491 | 1 | 4953 | 1 |
| 4492 | ], | 4954 | ], |
| 4493 | [ | 4955 | [ |
| 4494 | "{BD0EF658-4D0F-42A7-B546-B4F4A77D246B},1", | 4956 | "{551D2637-2AA7-4923-B2DD-31C2E1489FD2},1", |
| 4495 | 1 | 4957 | 1 |
| 4496 | ], | 4958 | ], |
| 4497 | [ | 4959 | [ |
| 4498 | "{6FEA70FA-1451-4A27-8A8E-C4CC3B713CF6},1", | 4960 | "{CBF82C86-3A8B-457F-92A4-1AC7BF436CE9},1", |
| 4499 | 1 | 4961 | 1 |
| 4500 | ], | 4962 | ], |
| 4501 | [ | 4963 | [ |
| 4502 | "{68A1E0E1-7A39-4AB6-AD71-4D8A18878D79},1", | 4964 | "{42010094-E377-4A97-9395-AD45808D0552},1", |
| 4503 | 1 | 4965 | 1 |
| 4504 | ], | 4966 | ], |
| 4505 | [ | 4967 | [ |
| 4506 | "{7E9041BD-301B-4B5B-9456-1C92050E4914},1", | 4968 | "{0F6ABB5E-A021-4E1D-B187-7A72E74000DC},1", |
| 4507 | 1 | 4969 | 1 |
| 4508 | ], | 4970 | ], |
| 4509 | [ | 4971 | [ |
| 4510 | "{EFA77BAE-8A98-4176-B536-B615D1D2ED2B},1", | 4972 | "{F9DA3139-5856-4B11-95C0-356683E3ACFE},1", |
| 4511 | 1 | 4973 | 1 |
| 4512 | ], | 4974 | ], |
| 4513 | [ | 4975 | [ |
| 4514 | "{87AC0239-51A5-47C6-BAF4-926CCF56385C},1", | 4976 | "{439B80E0-9D4C-4A2F-9163-4E301491364D},1", |
| 4515 | 1 | 4977 | 1 |
| 4516 | ], | 4978 | ], |
| 4517 | [ | 4979 | [ |
| 4518 | "{C50BF7FC-F848-4B91-B8C8-743D0AA5F195},1", | 4980 | "{B03B973C-50BC-4B9A-9914-B1D5C0CF6197},1", |
| 4519 | 1 | 4981 | 1 |
| 4520 | ], | 4982 | ], |
| 4521 | [ | 4983 | [ |
| 4522 | "{FD8D07D0-C447-4516-A7DB-F8522B77D4A7},1", | 4984 | "{A459C474-E6EB-4BE5-9193-271799B2860B},1", |
| 4523 | 1 | 4985 | 1 |
| 4524 | ], | 4986 | ], |
| 4525 | [ | 4987 | [ |
| 4526 | "{D2E1FE57-F45D-49C3-B388-95222ACA3886},1", | 4988 | "{4ED14CEE-8560-4125-B488-9945C116741E},1", |
| 4527 | 1 | 4989 | 1 |
| 4528 | ], | 4990 | ], |
| 4529 | [ | 4991 | [ |
| 4530 | "{7F6702B3-C977-436F-82AF-403E2BE950E7},1", | 4992 | "{C4FF8FE4-4C43-461F-98DD-EB1FF2A863B2},1", |
| 4531 | 1 | 4993 | 1 |
| 4532 | ], | 4994 | ], |
| 4533 | [ | 4995 | [ |
| 4534 | "{688C4EA1-80F5-4D85-B9BB-F8633FF3C0CA},1", | 4996 | "{932BE37F-05AA-4EFB-85D8-A33445E738B3},1", |
| 4535 | 1 | 4997 | 1 |
| 4536 | ], | 4998 | ], |
| 4537 | [ | 4999 | [ |
| 4538 | "{754FA957-C432-4502-A5CF-EE44449462C0},1", | 5000 | "{9B755368-1D49-4D7E-8D33-6DC536F57848},1", |
| 4539 | 1 | 5001 | 1 |
| 4540 | ], | 5002 | ], |
| 4541 | [ | 5003 | [ |
| 4542 | "{D2197C55-D962-4DF0-8B14-26F88C06D6FA},1", | 5004 | "{D045EAAF-ACE9-4B4F-979B-E348A4ADE693},1", |
| 4543 | 1 | 5005 | 1 |
| 4544 | ], | 5006 | ], |
| 4545 | [ | 5007 | [ |
| 4546 | "{87B83BA8-A906-4F71-A6CA-3E8133A75D34},1", | 5008 | "{5CA4C59F-B400-4BC6-8B90-30ECAC35CB43},1", |
| 4547 | 1 | 5009 | 1 |
| 4548 | ], | 5010 | ], |
| 4549 | [ | 5011 | [ |
| 4550 | "{F2D6AEBB-98EA-4F10-991F-5243E74AF450},1", | 5012 | "{D63FB383-FB37-44F9-AEBB-E868E903A2BB},1", |
| 4551 | 1 | 5013 | 1 |
| 4552 | ], | 5014 | ], |
| 4553 | [ | 5015 | [ |
| 4554 | "{406687D7-F9E9-491B-85DA-166DE6CD5A60},1", | 5016 | "{C6E54364-6CD0-4764-8180-873030B9EA53},1", |
| 4555 | 1 | 5017 | 1 |
| 4556 | ], | 5018 | ], |
| 4557 | [ | 5019 | [ |
| 4558 | "{1F73BF36-9FC2-495D-844D-B370D5C47DE7},1", | 5020 | "{DEEC391B-F2D1-4EB1-BEB1-57598DFBB982},1", |
| 4559 | 1 | 5021 | 1 |
| 4560 | ], | 5022 | ], |
| 4561 | [ | 5023 | [ |
| 4562 | "{E9BD343F-4A66-4DE2-81F9-3789CCA22818},1", | 5024 | "{9E7616ED-DE19-4625-8EA0-0C5DEB18B690},1", |
| 4563 | 1 | 5025 | 1 |
| 4564 | ], | 5026 | ], |
| 4565 | [ | 5027 | [ |
| 4566 | "{189EA744-BE13-467A-95BA-2667A134C0B0},1", | 5028 | "{89F1748D-6749-4AD0-BAF6-07E9F9C058E5},1", |
| 4567 | 1 | 5029 | 1 |
| 4568 | ], | 5030 | ], |
| 4569 | [ | 5031 | [ |
| 4570 | "{017F5881-F818-4C7C-B9F9-6BCB4085F1D0},1", | 5032 | "{00B10C58-6649-428D-A081-3EBBA6E88E97},1", |
| 4571 | 1 | 5033 | 1 |
| 4572 | ], | 5034 | ], |
| 4573 | [ | 5035 | [ |
| 4574 | "{CD0D17F4-25D8-4076-9AF7-47D9CDEFCF0A},1", | 5036 | "{58E99482-710A-4938-8CFB-88A1C9B946C9},1", |
| 4575 | 1 | 5037 | 1 |
| 4576 | ], | 5038 | ], |
| 4577 | [ | 5039 | [ |
| 4578 | "{CCBB5969-BCE8-498B-BE1A-98FD3A44DB1A},1", | 5040 | "{F721C9D5-9A6A-405F-94C9-9B85A7EEB980},1", |
| 4579 | 1 | 5041 | 1 |
| 4580 | ], | 5042 | ], |
| 4581 | [ | 5043 | [ |
| 4582 | "{2B736B19-2D9C-4E76-A732-478D533D717A},1", | 5044 | "{15A45C01-A9E8-42E2-A267-C7FCE728815E},1", |
| 4583 | 1 | 5045 | 1 |
| 4584 | ] | 5046 | ] |
| 4585 | ] | 5047 | ] |
| 4586 | } | 5048 | } |
| 4587 | }, | 5049 | }, |
| 4588 | "space": "{90FCDB0C-36AE-422F-B4FE-67676826FE36},1" | 5050 | "space": "{A5A2AFBE-1DF1-4190-AAC7-1AD29C9564F2},1" |
| 4589 | }, | 5051 | }, |
| 4590 | { | 5052 | { |
| 4591 | "id": 2, | 5053 | "id": 2, |
| 4592 | "operation": { | 5054 | "operation": { |
| 4593 | "Page": { | 5055 | "Page": { |
| 4594 | "after": { | 5056 | "after": { |
| 4595 | "created": 134335976560000000, | 5057 | "created": 134348779900000000, |
| 4596 | "definitions": { | 5058 | "definitions": { |
| 4597 | "{7F6702B3-C977-436F-82AF-403E2BE950E7},18": { | 5059 | "{C4FF8FE4-4C43-461F-98DD-EB1FF2A863B2},18": { |
| 4598 | "format": { | 5060 | "format": { |
| 4599 | "alignment": null, | 5061 | "alignment": null, |
| 4600 | "bold": null, | 5062 | "bold": null, |
| ... | @@ -4611,6 +5073,7 @@ | ... | @@ -4611,6 +5073,7 @@ |
| 4611 | "line_spacing": null, | 5073 | "line_spacing": null, |
| 4612 | "list_spacing": null, | 5074 | "list_spacing": null, |
| 4613 | "math": null, | 5075 | "math": null, |
| 5076 | "math_object": null, | ||
| 4614 | "rtl": null, | 5077 | "rtl": null, |
| 4615 | "space_after": null, | 5078 | "space_after": null, |
| 4616 | "space_before": null, | 5079 | "space_before": null, |
| ... | @@ -4625,6 +5088,24 @@ | ... | @@ -4625,6 +5088,24 @@ |
| 4625 | } | 5088 | } |
| 4626 | } | 5089 | } |
| 4627 | }, | 5090 | }, |
| 5091 | "identity": [ | ||
| 5092 | 228, | ||
| 5093 | 143, | ||
| 5094 | 255, | ||
| 5095 | 196, | ||
| 5096 | 67, | ||
| 5097 | 76, | ||
| 5098 | 31, | ||
| 5099 | 70, | ||
| 5100 | 152, | ||
| 5101 | 221, | ||
| 5102 | 235, | ||
| 5103 | 31, | ||
| 5104 | 242, | ||
| 5105 | 168, | ||
| 5106 | 99, | ||
| 5107 | 178 | ||
| 5108 | ], | ||
| 4628 | "margin_origin": [ | 5109 | "margin_origin": [ |
| 4629 | 0.0, | 5110 | 0.0, |
| 4630 | 0.0 | 5111 | 0.0 |
| ... | @@ -4632,7 +5113,7 @@ | ... | @@ -4632,7 +5113,7 @@ |
| 4632 | "objects": [ | 5113 | "objects": [ |
| 4633 | { | 5114 | { |
| 4634 | "Outline": { | 5115 | "Outline": { |
| 4635 | "id": "{F5ABCB2B-29D3-411E-A836-02A85749FF09},0", | 5116 | "id": "{91379CE6-91DA-48BA-B306-77E4CEB068B6},23", |
| 4636 | "indents": [], | 5117 | "indents": [], |
| 4637 | "layout": { | 5118 | "layout": { |
| 4638 | "max_height": null, | 5119 | "max_height": null, |
| ... | @@ -4649,7 +5130,7 @@ | ... | @@ -4649,7 +5130,7 @@ |
| 4649 | "content": { | 5130 | "content": { |
| 4650 | "Text": { | 5131 | "Text": { |
| 4651 | "date_field": null, | 5132 | "date_field": null, |
| 4652 | "id": "{3500B3EC-E613-4C17-8520-F7B22DB266FD},0", | 5133 | "id": "{91379CE6-91DA-48BA-B306-77E4CEB068B6},22", |
| 4653 | "tags": [], | 5134 | "tags": [], |
| 4654 | "text": { | 5135 | "text": { |
| 4655 | "spans": [ | 5136 | "spans": [ |
| ... | @@ -4671,6 +5152,7 @@ | ... | @@ -4671,6 +5152,7 @@ |
| 4671 | "line_spacing": null, | 5152 | "line_spacing": null, |
| 4672 | "list_spacing": null, | 5153 | "list_spacing": null, |
| 4673 | "math": null, | 5154 | "math": null, |
| 5155 | "math_object": null, | ||
| 4674 | "rtl": null, | 5156 | "rtl": null, |
| 4675 | "space_after": null, | 5157 | "space_after": null, |
| 4676 | "space_before": null, | 5158 | "space_before": null, |
| ... | @@ -4701,6 +5183,7 @@ | ... | @@ -4701,6 +5183,7 @@ |
| 4701 | "line_spacing": null, | 5183 | "line_spacing": null, |
| 4702 | "list_spacing": null, | 5184 | "list_spacing": null, |
| 4703 | "math": null, | 5185 | "math": null, |
| 5186 | "math_object": null, | ||
| 4704 | "rtl": null, | 5187 | "rtl": null, |
| 4705 | "space_after": null, | 5188 | "space_after": null, |
| 4706 | "space_before": null, | 5189 | "space_before": null, |
| ... | @@ -4709,9 +5192,13 @@ | ... | @@ -4709,9 +5192,13 @@ |
| 4709 | "superscript": null, | 5192 | "superscript": null, |
| 4710 | "underline": null | 5193 | "underline": null |
| 4711 | }, | 5194 | }, |
| 4712 | "id": "{DFF3BBDE-C14D-4D31-8E0D-CBCED9BB1195},0", | 5195 | "id": "{91379CE6-91DA-48BA-B306-77E4CEB068B6},24", |
| 4713 | "level": 1, | 5196 | "level": 1, |
| 4714 | "lists": [], | 5197 | "lists": [], |
| 5198 | "media": { | ||
| 5199 | "recordings": [], | ||
| 5200 | "time_ms": null | ||
| 5201 | }, | ||
| 4715 | "parent": null, | 5202 | "parent": null, |
| 4716 | "style": null, | 5203 | "style": null, |
| 4717 | "tags": [] | 5204 | "tags": [] |
| ... | @@ -4724,7 +5211,7 @@ | ... | @@ -4724,7 +5211,7 @@ |
| 4724 | { | 5211 | { |
| 4725 | "Title": { | 5212 | "Title": { |
| 4726 | "date": null, | 5213 | "date": null, |
| 4727 | "id": "{7F6702B3-C977-436F-82AF-403E2BE950E7},13", | 5214 | "id": "{C4FF8FE4-4C43-461F-98DD-EB1FF2A863B2},13", |
| 4728 | "layout": { | 5215 | "layout": { |
| 4729 | "max_height": null, | 5216 | "max_height": null, |
| 4730 | "max_width": null, | 5217 | "max_width": null, |
| ... | @@ -4735,24 +5222,29 @@ | ... | @@ -4735,24 +5222,29 @@ |
| 4735 | }, | 5222 | }, |
| 4736 | "outlines": [ | 5223 | "outlines": [ |
| 4737 | { | 5224 | { |
| 4738 | "id": "{7F6702B3-C977-436F-82AF-403E2BE950E7},14", | 5225 | "id": "{C4FF8FE4-4C43-461F-98DD-EB1FF2A863B2},14", |
| 4739 | "indents": [], | 5226 | "indents": [ |
| 5227 | 18.0, | ||
| 5228 | 0.0, | ||
| 5229 | 27.0, | ||
| 5230 | 27.0 | ||
| 5231 | ], | ||
| 4740 | "layout": { | 5232 | "layout": { |
| 4741 | "max_height": null, | 5233 | "max_height": 21.600000381469727, |
| 4742 | "max_width": null, | 5234 | "max_width": null, |
| 4743 | "reserved_width": null, | 5235 | "reserved_width": null, |
| 4744 | "width_set_by_user": null, | 5236 | "width_set_by_user": null, |
| 4745 | "x": null, | 5237 | "x": null, |
| 4746 | "y": null | 5238 | "y": null |
| 4747 | }, | 5239 | }, |
| 4748 | "min_width": null, | 5240 | "min_width": 162.0, |
| 4749 | "paragraphs": [ | 5241 | "paragraphs": [ |
| 4750 | { | 5242 | { |
| 4751 | "collapsed": false, | 5243 | "collapsed": false, |
| 4752 | "content": { | 5244 | "content": { |
| 4753 | "Text": { | 5245 | "Text": { |
| 4754 | "date_field": null, | 5246 | "date_field": null, |
| 4755 | "id": "{7F6702B3-C977-436F-82AF-403E2BE950E7},16", | 5247 | "id": "{C4FF8FE4-4C43-461F-98DD-EB1FF2A863B2},16", |
| 4756 | "tags": [], | 5248 | "tags": [], |
| 4757 | "text": { | 5249 | "text": { |
| 4758 | "spans": [ | 5250 | "spans": [ |
| ... | @@ -4774,6 +5266,7 @@ | ... | @@ -4774,6 +5266,7 @@ |
| 4774 | "line_spacing": null, | 5266 | "line_spacing": null, |
| 4775 | "list_spacing": null, | 5267 | "list_spacing": null, |
| 4776 | "math": null, | 5268 | "math": null, |
| 5269 | "math_object": null, | ||
| 4777 | "rtl": null, | 5270 | "rtl": null, |
| 4778 | "space_after": null, | 5271 | "space_after": null, |
| 4779 | "space_before": null, | 5272 | "space_before": null, |
| ... | @@ -4804,6 +5297,7 @@ | ... | @@ -4804,6 +5297,7 @@ |
| 4804 | "line_spacing": null, | 5297 | "line_spacing": null, |
| 4805 | "list_spacing": null, | 5298 | "list_spacing": null, |
| 4806 | "math": null, | 5299 | "math": null, |
| 5300 | "math_object": null, | ||
| 4807 | "rtl": null, | 5301 | "rtl": null, |
| 4808 | "space_after": null, | 5302 | "space_after": null, |
| 4809 | "space_before": null, | 5303 | "space_before": null, |
| ... | @@ -4812,15 +5306,19 @@ | ... | @@ -4812,15 +5306,19 @@ |
| 4812 | "superscript": null, | 5306 | "superscript": null, |
| 4813 | "underline": null | 5307 | "underline": null |
| 4814 | }, | 5308 | }, |
| 4815 | "id": "{7F6702B3-C977-436F-82AF-403E2BE950E7},15", | 5309 | "id": "{C4FF8FE4-4C43-461F-98DD-EB1FF2A863B2},15", |
| 4816 | "level": 1, | 5310 | "level": 1, |
| 4817 | "lists": [], | 5311 | "lists": [], |
| 5312 | "media": { | ||
| 5313 | "recordings": [], | ||
| 5314 | "time_ms": null | ||
| 5315 | }, | ||
| 4818 | "parent": null, | 5316 | "parent": null, |
| 4819 | "style": "{7F6702B3-C977-436F-82AF-403E2BE950E7},18", | 5317 | "style": "{C4FF8FE4-4C43-461F-98DD-EB1FF2A863B2},18", |
| 4820 | "tags": [] | 5318 | "tags": [] |
| 4821 | } | 5319 | } |
| 4822 | ], | 5320 | ], |
| 4823 | "title": false, | 5321 | "title": true, |
| 4824 | "unsupported": [] | 5322 | "unsupported": [] |
| 4825 | } | 5323 | } |
| 4826 | ] | 5324 | ] |
| ... | @@ -4831,9 +5329,9 @@ | ... | @@ -4831,9 +5329,9 @@ |
| 4831 | }, | 5329 | }, |
| 4832 | "author": "Author", | 5330 | "author": "Author", |
| 4833 | "before": { | 5331 | "before": { |
| 4834 | "created": 134335976560000000, | 5332 | "created": 134348779900000000, |
| 4835 | "definitions": { | 5333 | "definitions": { |
| 4836 | "{7F6702B3-C977-436F-82AF-403E2BE950E7},18": { | 5334 | "{C4FF8FE4-4C43-461F-98DD-EB1FF2A863B2},18": { |
| 4837 | "format": { | 5335 | "format": { |
| 4838 | "alignment": null, | 5336 | "alignment": null, |
| 4839 | "bold": null, | 5337 | "bold": null, |
| ... | @@ -4850,6 +5348,7 @@ | ... | @@ -4850,6 +5348,7 @@ |
| 4850 | "line_spacing": null, | 5348 | "line_spacing": null, |
| 4851 | "list_spacing": null, | 5349 | "list_spacing": null, |
| 4852 | "math": null, | 5350 | "math": null, |
| 5351 | "math_object": null, | ||
| 4853 | "rtl": null, | 5352 | "rtl": null, |
| 4854 | "space_after": null, | 5353 | "space_after": null, |
| 4855 | "space_before": null, | 5354 | "space_before": null, |
| ... | @@ -4864,6 +5363,24 @@ | ... | @@ -4864,6 +5363,24 @@ |
| 4864 | } | 5363 | } |
| 4865 | } | 5364 | } |
| 4866 | }, | 5365 | }, |
| 5366 | "identity": [ | ||
| 5367 | 228, | ||
| 5368 | 143, | ||
| 5369 | 255, | ||
| 5370 | 196, | ||
| 5371 | 67, | ||
| 5372 | 76, | ||
| 5373 | 31, | ||
| 5374 | 70, | ||
| 5375 | 152, | ||
| 5376 | 221, | ||
| 5377 | 235, | ||
| 5378 | 31, | ||
| 5379 | 242, | ||
| 5380 | 168, | ||
| 5381 | 99, | ||
| 5382 | 178 | ||
| 5383 | ], | ||
| 4867 | "margin_origin": [ | 5384 | "margin_origin": [ |
| 4868 | 0.0, | 5385 | 0.0, |
| 4869 | 0.0 | 5386 | 0.0 |
| ... | @@ -4872,7 +5389,7 @@ | ... | @@ -4872,7 +5389,7 @@ |
| 4872 | { | 5389 | { |
| 4873 | "Title": { | 5390 | "Title": { |
| 4874 | "date": null, | 5391 | "date": null, |
| 4875 | "id": "{7F6702B3-C977-436F-82AF-403E2BE950E7},13", | 5392 | "id": "{C4FF8FE4-4C43-461F-98DD-EB1FF2A863B2},13", |
| 4876 | "layout": { | 5393 | "layout": { |
| 4877 | "max_height": null, | 5394 | "max_height": null, |
| 4878 | "max_width": null, | 5395 | "max_width": null, |
| ... | @@ -4883,24 +5400,29 @@ | ... | @@ -4883,24 +5400,29 @@ |
| 4883 | }, | 5400 | }, |
| 4884 | "outlines": [ | 5401 | "outlines": [ |
| 4885 | { | 5402 | { |
| 4886 | "id": "{7F6702B3-C977-436F-82AF-403E2BE950E7},14", | 5403 | "id": "{C4FF8FE4-4C43-461F-98DD-EB1FF2A863B2},14", |
| 4887 | "indents": [], | 5404 | "indents": [ |
| 5405 | 18.0, | ||
| 5406 | 0.0, | ||
| 5407 | 27.0, | ||
| 5408 | 27.0 | ||
| 5409 | ], | ||
| 4888 | "layout": { | 5410 | "layout": { |
| 4889 | "max_height": null, | 5411 | "max_height": 21.600000381469727, |
| 4890 | "max_width": null, | 5412 | "max_width": null, |
| 4891 | "reserved_width": null, | 5413 | "reserved_width": null, |
| 4892 | "width_set_by_user": null, | 5414 | "width_set_by_user": null, |
| 4893 | "x": null, | 5415 | "x": null, |
| 4894 | "y": null | 5416 | "y": null |
| 4895 | }, | 5417 | }, |
| 4896 | "min_width": null, | 5418 | "min_width": 162.0, |
| 4897 | "paragraphs": [ | 5419 | "paragraphs": [ |
| 4898 | { | 5420 | { |
| 4899 | "collapsed": false, | 5421 | "collapsed": false, |
| 4900 | "content": { | 5422 | "content": { |
| 4901 | "Text": { | 5423 | "Text": { |
| 4902 | "date_field": null, | 5424 | "date_field": null, |
| 4903 | "id": "{7F6702B3-C977-436F-82AF-403E2BE950E7},16", | 5425 | "id": "{C4FF8FE4-4C43-461F-98DD-EB1FF2A863B2},16", |
| 4904 | "tags": [], | 5426 | "tags": [], |
| 4905 | "text": { | 5427 | "text": { |
| 4906 | "spans": [ | 5428 | "spans": [ |
| ... | @@ -4922,6 +5444,7 @@ | ... | @@ -4922,6 +5444,7 @@ |
| 4922 | "line_spacing": null, | 5444 | "line_spacing": null, |
| 4923 | "list_spacing": null, | 5445 | "list_spacing": null, |
| 4924 | "math": null, | 5446 | "math": null, |
| 5447 | "math_object": null, | ||
| 4925 | "rtl": null, | 5448 | "rtl": null, |
| 4926 | "space_after": null, | 5449 | "space_after": null, |
| 4927 | "space_before": null, | 5450 | "space_before": null, |
| ... | @@ -4952,6 +5475,7 @@ | ... | @@ -4952,6 +5475,7 @@ |
| 4952 | "line_spacing": null, | 5475 | "line_spacing": null, |
| 4953 | "list_spacing": null, | 5476 | "list_spacing": null, |
| 4954 | "math": null, | 5477 | "math": null, |
| 5478 | "math_object": null, | ||
| 4955 | "rtl": null, | 5479 | "rtl": null, |
| 4956 | "space_after": null, | 5480 | "space_after": null, |
| 4957 | "space_before": null, | 5481 | "space_before": null, |
| ... | @@ -4960,15 +5484,19 @@ | ... | @@ -4960,15 +5484,19 @@ |
| 4960 | "superscript": null, | 5484 | "superscript": null, |
| 4961 | "underline": null | 5485 | "underline": null |
| 4962 | }, | 5486 | }, |
| 4963 | "id": "{7F6702B3-C977-436F-82AF-403E2BE950E7},15", | 5487 | "id": "{C4FF8FE4-4C43-461F-98DD-EB1FF2A863B2},15", |
| 4964 | "level": 1, | 5488 | "level": 1, |
| 4965 | "lists": [], | 5489 | "lists": [], |
| 5490 | "media": { | ||
| 5491 | "recordings": [], | ||
| 5492 | "time_ms": null | ||
| 5493 | }, | ||
| 4966 | "parent": null, | 5494 | "parent": null, |
| 4967 | "style": "{7F6702B3-C977-436F-82AF-403E2BE950E7},18", | 5495 | "style": "{C4FF8FE4-4C43-461F-98DD-EB1FF2A863B2},18", |
| 4968 | "tags": [] | 5496 | "tags": [] |
| 4969 | } | 5497 | } |
| 4970 | ], | 5498 | ], |
| 4971 | "title": false, | 5499 | "title": true, |
| 4972 | "unsupported": [] | 5500 | "unsupported": [] |
| 4973 | } | 5501 | } |
| 4974 | ] | 5502 | ] |
| ... | @@ -4979,7 +5507,7 @@ | ... | @@ -4979,7 +5507,7 @@ |
| 4979 | } | 5507 | } |
| 4980 | } | 5508 | } |
| 4981 | }, | 5509 | }, |
| 4982 | "space": "{7F6702B3-C977-436F-82AF-403E2BE950E7},1" | 5510 | "space": "{C4FF8FE4-4C43-461F-98DD-EB1FF2A863B2},1" |
| 4983 | } | 5511 | } |
| 4984 | ], | 5512 | ], |
| 4985 | [ | 5513 | [ |
| ... | @@ -4990,215 +5518,215 @@ | ... | @@ -4990,215 +5518,215 @@ |
| 4990 | "edits": [ | 5518 | "edits": [ |
| 4991 | { | 5519 | { |
| 4992 | "guid": [ | 5520 | "guid": [ |
| 5521 | 171, | ||
| 5522 | 123, | ||
| 5523 | 160, | ||
| 5524 | 66, | ||
| 5525 | 159, | ||
| 5526 | 123, | ||
| 5527 | 23, | ||
| 5528 | 73, | ||
| 5529 | 161, | ||
| 4993 | 60, | 5530 | 60, |
| 4994 | 96, | 5531 | 51, |
| 4995 | 70, | 5532 | 152, |
| 4996 | 223, | 5533 | 144, |
| 4997 | 197, | 5534 | 104, |
| 4998 | 137, | 5535 | 92, |
| 4999 | 63, | 5536 | 228 |
| 5000 | 78, | ||
| 5001 | 170, | ||
| 5002 | 199, | ||
| 5003 | 81, | ||
| 5004 | 158, | ||
| 5005 | 99, | ||
| 5006 | 18, | ||
| 5007 | 163, | ||
| 5008 | 133 | ||
| 5009 | ], | 5537 | ], |
| 5010 | "level": 1, | 5538 | "level": 1, |
| 5011 | "position": { | 5539 | "position": { |
| 5012 | "Before": "{754FA957-C432-4502-A5CF-EE44449462C0},1" | 5540 | "Before": "{9B755368-1D49-4D7E-8D33-6DC536F57848},1" |
| 5013 | }, | 5541 | }, |
| 5014 | "space": "{D2197C55-D962-4DF0-8B14-26F88C06D6FA},1" | 5542 | "space": "{D045EAAF-ACE9-4B4F-979B-E348A4ADE693},1" |
| 5015 | }, | 5543 | }, |
| 5016 | { | 5544 | { |
| 5017 | "guid": [ | 5545 | "guid": [ |
| 5018 | 218, | 5546 | 243, |
| 5019 | 181, | ||
| 5020 | 208, | ||
| 5021 | 213, | ||
| 5022 | 72, | ||
| 5023 | 152, | ||
| 5024 | 253, | ||
| 5025 | 76, | 5547 | 76, |
| 5026 | 176, | 5548 | 116, |
| 5027 | 213, | 5549 | 146, |
| 5028 | 138, | 5550 | 38, |
| 5029 | 85, | 5551 | 89, |
| 5030 | 141, | 5552 | 244, |
| 5031 | 127, | 5553 | 65, |
| 5032 | 170, | 5554 | 170, |
| 5033 | 160 | 5555 | 135, |
| 5556 | 180, | ||
| 5557 | 163, | ||
| 5558 | 152, | ||
| 5559 | 15, | ||
| 5560 | 148, | ||
| 5561 | 54 | ||
| 5034 | ], | 5562 | ], |
| 5035 | "level": 2, | 5563 | "level": 2, |
| 5036 | "position": "Keep", | 5564 | "position": "Keep", |
| 5037 | "space": "{87B83BA8-A906-4F71-A6CA-3E8133A75D34},1" | 5565 | "space": "{5CA4C59F-B400-4BC6-8B90-30ECAC35CB43},1" |
| 5038 | } | 5566 | } |
| 5039 | ], | 5567 | ], |
| 5040 | "observed": [ | 5568 | "observed": [ |
| 5041 | [ | 5569 | [ |
| 5042 | "{869D5929-6E5E-4EDB-9639-4C56977DD447},1", | 5570 | "{0B37E922-551A-48F8-BCAC-4AB3503B4D28},1", |
| 5043 | 1 | 5571 | 1 |
| 5044 | ], | 5572 | ], |
| 5045 | [ | 5573 | [ |
| 5046 | "{B0B53F94-0105-4C59-B65B-9473F49BFF41},1", | 5574 | "{74492C4A-164B-40E9-B297-4D3B4685347D},1", |
| 5047 | 1 | 5575 | 1 |
| 5048 | ], | 5576 | ], |
| 5049 | [ | 5577 | [ |
| 5050 | "{0E98EE82-B78F-4872-8BD9-5D9B23A817C5},1", | 5578 | "{DAFF533D-608B-4735-BA1D-2354E8CB8A69},1", |
| 5051 | 1 | 5579 | 1 |
| 5052 | ], | 5580 | ], |
| 5053 | [ | 5581 | [ |
| 5054 | "{C837A033-F055-4455-B45F-17D82016921C},1", | 5582 | "{8B766F11-2A4F-4D95-8473-D3CA68129E11},1", |
| 5055 | 1 | 5583 | 1 |
| 5056 | ], | 5584 | ], |
| 5057 | [ | 5585 | [ |
| 5058 | "{89D86121-BF5C-418B-BB92-CBFFA8EF545F},1", | 5586 | "{28E0CC5F-68BC-4A86-9ED7-114FFD3BAE9A},1", |
| 5059 | 1 | 5587 | 1 |
| 5060 | ], | 5588 | ], |
| 5061 | [ | 5589 | [ |
| 5062 | "{57E835EA-2799-4118-B55C-7BB1E51C6941},1", | 5590 | "{31FFA5F9-C1B8-412D-A2D6-7ACC3CC16FD0},1", |
| 5063 | 1 | 5591 | 1 |
| 5064 | ], | 5592 | ], |
| 5065 | [ | 5593 | [ |
| 5066 | "{F7EECCCF-B6B4-44F2-A543-36837EC18B8A},1", | 5594 | "{AFCD09F6-1085-4582-BDB2-4B91E15D2088},1", |
| 5067 | 1 | 5595 | 1 |
| 5068 | ], | 5596 | ], |
| 5069 | [ | 5597 | [ |
| 5070 | "{361A0F09-EA63-4F2D-9620-C061D4E4707E},1", | 5598 | "{A94D5882-C902-4CAE-B38E-2ED56EEF177F},1", |
| 5071 | 1 | 5599 | 1 |
| 5072 | ], | 5600 | ], |
| 5073 | [ | 5601 | [ |
| 5074 | "{EB3940F1-F255-42BE-8C31-DD09E86DBAAF},1", | 5602 | "{5AB37A0F-5702-4C8E-A8C6-79ED6ED1E69F},1", |
| 5075 | 1 | 5603 | 1 |
| 5076 | ], | 5604 | ], |
| 5077 | [ | 5605 | [ |
| 5078 | "{C2870D07-8ADC-44BB-9F12-15D6C3708689},1", | 5606 | "{F212795E-0E4B-4844-A8A6-ABD7F7025124},1", |
| 5079 | 1 | 5607 | 1 |
| 5080 | ], | 5608 | ], |
| 5081 | [ | 5609 | [ |
| 5082 | "{1E0C6331-32C9-4EF9-8087-150A8669FA91},1", | 5610 | "{1786F2CA-0A43-4081-8B55-B207EEE0331C},1", |
| 5083 | 1 | 5611 | 1 |
| 5084 | ], | 5612 | ], |
| 5085 | [ | 5613 | [ |
| 5086 | "{ED9769DD-99D6-4BE2-B980-580DA9C4DFCE},1", | 5614 | "{3E966EF8-B7B4-483F-B241-8F21DF318AB3},1", |
| 5087 | 1 | 5615 | 1 |
| 5088 | ], | 5616 | ], |
| 5089 | [ | 5617 | [ |
| 5090 | "{17C30054-7129-4A10-9D00-4D5545E4F4BA},1", | 5618 | "{0D361C76-92FD-4B76-901B-809C2C3C3CE6},1", |
| 5091 | 1 | 5619 | 1 |
| 5092 | ], | 5620 | ], |
| 5093 | [ | 5621 | [ |
| 5094 | "{4C5FBA5B-E348-4750-ACE4-E9694D527BFF},1", | 5622 | "{AE89305A-73F4-46B5-B41D-18884685D896},1", |
| 5095 | 1 | 5623 | 1 |
| 5096 | ], | 5624 | ], |
| 5097 | [ | 5625 | [ |
| 5098 | "{BD0EF658-4D0F-42A7-B546-B4F4A77D246B},1", | 5626 | "{551D2637-2AA7-4923-B2DD-31C2E1489FD2},1", |
| 5099 | 1 | 5627 | 1 |
| 5100 | ], | 5628 | ], |
| 5101 | [ | 5629 | [ |
| 5102 | "{6FEA70FA-1451-4A27-8A8E-C4CC3B713CF6},1", | 5630 | "{CBF82C86-3A8B-457F-92A4-1AC7BF436CE9},1", |
| 5103 | 1 | 5631 | 1 |
| 5104 | ], | 5632 | ], |
| 5105 | [ | 5633 | [ |
| 5106 | "{68A1E0E1-7A39-4AB6-AD71-4D8A18878D79},1", | 5634 | "{42010094-E377-4A97-9395-AD45808D0552},1", |
| 5107 | 1 | 5635 | 1 |
| 5108 | ], | 5636 | ], |
| 5109 | [ | 5637 | [ |
| 5110 | "{7E9041BD-301B-4B5B-9456-1C92050E4914},1", | 5638 | "{0F6ABB5E-A021-4E1D-B187-7A72E74000DC},1", |
| 5111 | 1 | 5639 | 1 |
| 5112 | ], | 5640 | ], |
| 5113 | [ | 5641 | [ |
| 5114 | "{EFA77BAE-8A98-4176-B536-B615D1D2ED2B},1", | 5642 | "{F9DA3139-5856-4B11-95C0-356683E3ACFE},1", |
| 5115 | 1 | 5643 | 1 |
| 5116 | ], | 5644 | ], |
| 5117 | [ | 5645 | [ |
| 5118 | "{87AC0239-51A5-47C6-BAF4-926CCF56385C},1", | 5646 | "{439B80E0-9D4C-4A2F-9163-4E301491364D},1", |
| 5119 | 1 | 5647 | 1 |
| 5120 | ], | 5648 | ], |
| 5121 | [ | 5649 | [ |
| 5122 | "{C50BF7FC-F848-4B91-B8C8-743D0AA5F195},1", | 5650 | "{B03B973C-50BC-4B9A-9914-B1D5C0CF6197},1", |
| 5123 | 1 | 5651 | 1 |
| 5124 | ], | 5652 | ], |
| 5125 | [ | 5653 | [ |
| 5126 | "{FD8D07D0-C447-4516-A7DB-F8522B77D4A7},1", | 5654 | "{A459C474-E6EB-4BE5-9193-271799B2860B},1", |
| 5127 | 1 | 5655 | 1 |
| 5128 | ], | 5656 | ], |
| 5129 | [ | 5657 | [ |
| 5130 | "{D2E1FE57-F45D-49C3-B388-95222ACA3886},1", | 5658 | "{4ED14CEE-8560-4125-B488-9945C116741E},1", |
| 5131 | 1 | 5659 | 1 |
| 5132 | ], | 5660 | ], |
| 5133 | [ | 5661 | [ |
| 5134 | "{7F6702B3-C977-436F-82AF-403E2BE950E7},1", | 5662 | "{C4FF8FE4-4C43-461F-98DD-EB1FF2A863B2},1", |
| 5135 | 1 | 5663 | 1 |
| 5136 | ], | 5664 | ], |
| 5137 | [ | 5665 | [ |
| 5138 | "{688C4EA1-80F5-4D85-B9BB-F8633FF3C0CA},1", | 5666 | "{932BE37F-05AA-4EFB-85D8-A33445E738B3},1", |
| 5139 | 1 | 5667 | 1 |
| 5140 | ], | 5668 | ], |
| 5141 | [ | 5669 | [ |
| 5142 | "{754FA957-C432-4502-A5CF-EE44449462C0},1", | 5670 | "{9B755368-1D49-4D7E-8D33-6DC536F57848},1", |
| 5143 | 1 | 5671 | 1 |
| 5144 | ], | 5672 | ], |
| 5145 | [ | 5673 | [ |
| 5146 | "{D2197C55-D962-4DF0-8B14-26F88C06D6FA},1", | 5674 | "{D045EAAF-ACE9-4B4F-979B-E348A4ADE693},1", |
| 5147 | 1 | 5675 | 1 |
| 5148 | ], | 5676 | ], |
| 5149 | [ | 5677 | [ |
| 5150 | "{87B83BA8-A906-4F71-A6CA-3E8133A75D34},1", | 5678 | "{5CA4C59F-B400-4BC6-8B90-30ECAC35CB43},1", |
| 5151 | 1 | 5679 | 1 |
| 5152 | ], | 5680 | ], |
| 5153 | [ | 5681 | [ |
| 5154 | "{F2D6AEBB-98EA-4F10-991F-5243E74AF450},1", | 5682 | "{D63FB383-FB37-44F9-AEBB-E868E903A2BB},1", |
| 5155 | 1 | 5683 | 1 |
| 5156 | ], | 5684 | ], |
| 5157 | [ | 5685 | [ |
| 5158 | "{406687D7-F9E9-491B-85DA-166DE6CD5A60},1", | 5686 | "{C6E54364-6CD0-4764-8180-873030B9EA53},1", |
| 5159 | 1 | 5687 | 1 |
| 5160 | ], | 5688 | ], |
| 5161 | [ | 5689 | [ |
| 5162 | "{1F73BF36-9FC2-495D-844D-B370D5C47DE7},1", | 5690 | "{DEEC391B-F2D1-4EB1-BEB1-57598DFBB982},1", |
| 5163 | 1 | 5691 | 1 |
| 5164 | ], | 5692 | ], |
| 5165 | [ | 5693 | [ |
| 5166 | "{E9BD343F-4A66-4DE2-81F9-3789CCA22818},1", | 5694 | "{9E7616ED-DE19-4625-8EA0-0C5DEB18B690},1", |
| 5167 | 1 | 5695 | 1 |
| 5168 | ], | 5696 | ], |
| 5169 | [ | 5697 | [ |
| 5170 | "{189EA744-BE13-467A-95BA-2667A134C0B0},1", | 5698 | "{89F1748D-6749-4AD0-BAF6-07E9F9C058E5},1", |
| 5171 | 1 | 5699 | 1 |
| 5172 | ], | 5700 | ], |
| 5173 | [ | 5701 | [ |
| 5174 | "{017F5881-F818-4C7C-B9F9-6BCB4085F1D0},1", | 5702 | "{00B10C58-6649-428D-A081-3EBBA6E88E97},1", |
| 5175 | 1 | 5703 | 1 |
| 5176 | ], | 5704 | ], |
| 5177 | [ | 5705 | [ |
| 5178 | "{CD0D17F4-25D8-4076-9AF7-47D9CDEFCF0A},1", | 5706 | "{58E99482-710A-4938-8CFB-88A1C9B946C9},1", |
| 5179 | 1 | 5707 | 1 |
| 5180 | ], | 5708 | ], |
| 5181 | [ | 5709 | [ |
| 5182 | "{CCBB5969-BCE8-498B-BE1A-98FD3A44DB1A},1", | 5710 | "{F721C9D5-9A6A-405F-94C9-9B85A7EEB980},1", |
| 5183 | 1 | 5711 | 1 |
| 5184 | ], | 5712 | ], |
| 5185 | [ | 5713 | [ |
| 5186 | "{2B736B19-2D9C-4E76-A732-478D533D717A},1", | 5714 | "{15A45C01-A9E8-42E2-A267-C7FCE728815E},1", |
| 5187 | 1 | 5715 | 1 |
| 5188 | ] | 5716 | ] |
| 5189 | ] | 5717 | ] |
| 5190 | } | 5718 | } |
| 5191 | }, | 5719 | }, |
| 5192 | "space": "{90FCDB0C-36AE-422F-B4FE-67676826FE36},1" | 5720 | "space": "{A5A2AFBE-1DF1-4190-AAC7-1AD29C9564F2},1" |
| 5193 | }, | 5721 | }, |
| 5194 | { | 5722 | { |
| 5195 | "id": 2, | 5723 | "id": 2, |
| 5196 | "operation": { | 5724 | "operation": { |
| 5197 | "Page": { | 5725 | "Page": { |
| 5198 | "after": { | 5726 | "after": { |
| 5199 | "created": 134335976560000000, | 5727 | "created": 134348779900000000, |
| 5200 | "definitions": { | 5728 | "definitions": { |
| 5201 | "{D2197C55-D962-4DF0-8B14-26F88C06D6FA},18": { | 5729 | "{D045EAAF-ACE9-4B4F-979B-E348A4ADE693},18": { |
| 5202 | "format": { | 5730 | "format": { |
| 5203 | "alignment": null, | 5731 | "alignment": null, |
| 5204 | "bold": null, | 5732 | "bold": null, |
| ... | @@ -5215,6 +5743,7 @@ | ... | @@ -5215,6 +5743,7 @@ |
| 5215 | "line_spacing": null, | 5743 | "line_spacing": null, |
| 5216 | "list_spacing": null, | 5744 | "list_spacing": null, |
| 5217 | "math": null, | 5745 | "math": null, |
| 5746 | "math_object": null, | ||
| 5218 | "rtl": null, | 5747 | "rtl": null, |
| 5219 | "space_after": null, | 5748 | "space_after": null, |
| 5220 | "space_before": null, | 5749 | "space_before": null, |
| ... | @@ -5229,6 +5758,24 @@ | ... | @@ -5229,6 +5758,24 @@ |
| 5229 | } | 5758 | } |
| 5230 | } | 5759 | } |
| 5231 | }, | 5760 | }, |
| 5761 | "identity": [ | ||
| 5762 | 175, | ||
| 5763 | 234, | ||
| 5764 | 69, | ||
| 5765 | 208, | ||
| 5766 | 233, | ||
| 5767 | 172, | ||
| 5768 | 79, | ||
| 5769 | 75, | ||
| 5770 | 151, | ||
| 5771 | 155, | ||
| 5772 | 227, | ||
| 5773 | 72, | ||
| 5774 | 164, | ||
| 5775 | 173, | ||
| 5776 | 230, | ||
| 5777 | 147 | ||
| 5778 | ], | ||
| 5232 | "margin_origin": [ | 5779 | "margin_origin": [ |
| 5233 | 0.0, | 5780 | 0.0, |
| 5234 | 0.0 | 5781 | 0.0 |
| ... | @@ -5236,7 +5783,7 @@ | ... | @@ -5236,7 +5783,7 @@ |
| 5236 | "objects": [ | 5783 | "objects": [ |
| 5237 | { | 5784 | { |
| 5238 | "Outline": { | 5785 | "Outline": { |
| 5239 | "id": "{5A041F4B-D387-4A35-8723-9F16A833AC7E},0", | 5786 | "id": "{91379CE6-91DA-48BA-B306-77E4CEB068B6},26", |
| 5240 | "indents": [], | 5787 | "indents": [], |
| 5241 | "layout": { | 5788 | "layout": { |
| 5242 | "max_height": null, | 5789 | "max_height": null, |
| ... | @@ -5253,7 +5800,7 @@ | ... | @@ -5253,7 +5800,7 @@ |
| 5253 | "content": { | 5800 | "content": { |
| 5254 | "Text": { | 5801 | "Text": { |
| 5255 | "date_field": null, | 5802 | "date_field": null, |
| 5256 | "id": "{8A12422A-9108-4831-9889-D95B1B47F85A},0", | 5803 | "id": "{91379CE6-91DA-48BA-B306-77E4CEB068B6},25", |
| 5257 | "tags": [], | 5804 | "tags": [], |
| 5258 | "text": { | 5805 | "text": { |
| 5259 | "spans": [ | 5806 | "spans": [ |
| ... | @@ -5275,6 +5822,7 @@ | ... | @@ -5275,6 +5822,7 @@ |
| 5275 | "line_spacing": null, | 5822 | "line_spacing": null, |
| 5276 | "list_spacing": null, | 5823 | "list_spacing": null, |
| 5277 | "math": null, | 5824 | "math": null, |
| 5825 | "math_object": null, | ||
| 5278 | "rtl": null, | 5826 | "rtl": null, |
| 5279 | "space_after": null, | 5827 | "space_after": null, |
| 5280 | "space_before": null, | 5828 | "space_before": null, |
| ... | @@ -5305,6 +5853,7 @@ | ... | @@ -5305,6 +5853,7 @@ |
| 5305 | "line_spacing": null, | 5853 | "line_spacing": null, |
| 5306 | "list_spacing": null, | 5854 | "list_spacing": null, |
| 5307 | "math": null, | 5855 | "math": null, |
| 5856 | "math_object": null, | ||
| 5308 | "rtl": null, | 5857 | "rtl": null, |
| 5309 | "space_after": null, | 5858 | "space_after": null, |
| 5310 | "space_before": null, | 5859 | "space_before": null, |
| ... | @@ -5313,9 +5862,13 @@ | ... | @@ -5313,9 +5862,13 @@ |
| 5313 | "superscript": null, | 5862 | "superscript": null, |
| 5314 | "underline": null | 5863 | "underline": null |
| 5315 | }, | 5864 | }, |
| 5316 | "id": "{8906C7DF-89B1-40E4-9D47-6C794C39F974},0", | 5865 | "id": "{91379CE6-91DA-48BA-B306-77E4CEB068B6},27", |
| 5317 | "level": 1, | 5866 | "level": 1, |
| 5318 | "lists": [], | 5867 | "lists": [], |
| 5868 | "media": { | ||
| 5869 | "recordings": [], | ||
| 5870 | "time_ms": null | ||
| 5871 | }, | ||
| 5319 | "parent": null, | 5872 | "parent": null, |
| 5320 | "style": null, | 5873 | "style": null, |
| 5321 | "tags": [] | 5874 | "tags": [] |
| ... | @@ -5328,7 +5881,7 @@ | ... | @@ -5328,7 +5881,7 @@ |
| 5328 | { | 5881 | { |
| 5329 | "Title": { | 5882 | "Title": { |
| 5330 | "date": null, | 5883 | "date": null, |
| 5331 | "id": "{D2197C55-D962-4DF0-8B14-26F88C06D6FA},13", | 5884 | "id": "{D045EAAF-ACE9-4B4F-979B-E348A4ADE693},13", |
| 5332 | "layout": { | 5885 | "layout": { |
| 5333 | "max_height": null, | 5886 | "max_height": null, |
| 5334 | "max_width": null, | 5887 | "max_width": null, |
| ... | @@ -5339,24 +5892,29 @@ | ... | @@ -5339,24 +5892,29 @@ |
| 5339 | }, | 5892 | }, |
| 5340 | "outlines": [ | 5893 | "outlines": [ |
| 5341 | { | 5894 | { |
| 5342 | "id": "{D2197C55-D962-4DF0-8B14-26F88C06D6FA},14", | 5895 | "id": "{D045EAAF-ACE9-4B4F-979B-E348A4ADE693},14", |
| 5343 | "indents": [], | 5896 | "indents": [ |
| 5897 | 18.0, | ||
| 5898 | 0.0, | ||
| 5899 | 27.0, | ||
| 5900 | 27.0 | ||
| 5901 | ], | ||
| 5344 | "layout": { | 5902 | "layout": { |
| 5345 | "max_height": null, | 5903 | "max_height": 21.600000381469727, |
| 5346 | "max_width": null, | 5904 | "max_width": null, |
| 5347 | "reserved_width": null, | 5905 | "reserved_width": null, |
| 5348 | "width_set_by_user": null, | 5906 | "width_set_by_user": null, |
| 5349 | "x": null, | 5907 | "x": null, |
| 5350 | "y": null | 5908 | "y": null |
| 5351 | }, | 5909 | }, |
| 5352 | "min_width": null, | 5910 | "min_width": 162.0, |
| 5353 | "paragraphs": [ | 5911 | "paragraphs": [ |
| 5354 | { | 5912 | { |
| 5355 | "collapsed": false, | 5913 | "collapsed": false, |
| 5356 | "content": { | 5914 | "content": { |
| 5357 | "Text": { | 5915 | "Text": { |
| 5358 | "date_field": null, | 5916 | "date_field": null, |
| 5359 | "id": "{D2197C55-D962-4DF0-8B14-26F88C06D6FA},16", | 5917 | "id": "{D045EAAF-ACE9-4B4F-979B-E348A4ADE693},16", |
| 5360 | "tags": [], | 5918 | "tags": [], |
| 5361 | "text": { | 5919 | "text": { |
| 5362 | "spans": [ | 5920 | "spans": [ |
| ... | @@ -5378,6 +5936,7 @@ | ... | @@ -5378,6 +5936,7 @@ |
| 5378 | "line_spacing": null, | 5936 | "line_spacing": null, |
| 5379 | "list_spacing": null, | 5937 | "list_spacing": null, |
| 5380 | "math": null, | 5938 | "math": null, |
| 5939 | "math_object": null, | ||
| 5381 | "rtl": null, | 5940 | "rtl": null, |
| 5382 | "space_after": null, | 5941 | "space_after": null, |
| 5383 | "space_before": null, | 5942 | "space_before": null, |
| ... | @@ -5408,6 +5967,7 @@ | ... | @@ -5408,6 +5967,7 @@ |
| 5408 | "line_spacing": null, | 5967 | "line_spacing": null, |
| 5409 | "list_spacing": null, | 5968 | "list_spacing": null, |
| 5410 | "math": null, | 5969 | "math": null, |
| 5970 | "math_object": null, | ||
| 5411 | "rtl": null, | 5971 | "rtl": null, |
| 5412 | "space_after": null, | 5972 | "space_after": null, |
| 5413 | "space_before": null, | 5973 | "space_before": null, |
| ... | @@ -5416,15 +5976,19 @@ | ... | @@ -5416,15 +5976,19 @@ |
| 5416 | "superscript": null, | 5976 | "superscript": null, |
| 5417 | "underline": null | 5977 | "underline": null |
| 5418 | }, | 5978 | }, |
| 5419 | "id": "{D2197C55-D962-4DF0-8B14-26F88C06D6FA},15", | 5979 | "id": "{D045EAAF-ACE9-4B4F-979B-E348A4ADE693},15", |
| 5420 | "level": 1, | 5980 | "level": 1, |
| 5421 | "lists": [], | 5981 | "lists": [], |
| 5982 | "media": { | ||
| 5983 | "recordings": [], | ||
| 5984 | "time_ms": null | ||
| 5985 | }, | ||
| 5422 | "parent": null, | 5986 | "parent": null, |
| 5423 | "style": "{D2197C55-D962-4DF0-8B14-26F88C06D6FA},18", | 5987 | "style": "{D045EAAF-ACE9-4B4F-979B-E348A4ADE693},18", |
| 5424 | "tags": [] | 5988 | "tags": [] |
| 5425 | } | 5989 | } |
| 5426 | ], | 5990 | ], |
| 5427 | "title": false, | 5991 | "title": true, |
| 5428 | "unsupported": [] | 5992 | "unsupported": [] |
| 5429 | } | 5993 | } |
| 5430 | ] | 5994 | ] |
| ... | @@ -5435,9 +5999,9 @@ | ... | @@ -5435,9 +5999,9 @@ |
| 5435 | }, | 5999 | }, |
| 5436 | "author": "Author", | 6000 | "author": "Author", |
| 5437 | "before": { | 6001 | "before": { |
| 5438 | "created": 134335976560000000, | 6002 | "created": 134348779900000000, |
| 5439 | "definitions": { | 6003 | "definitions": { |
| 5440 | "{D2197C55-D962-4DF0-8B14-26F88C06D6FA},18": { | 6004 | "{D045EAAF-ACE9-4B4F-979B-E348A4ADE693},18": { |
| 5441 | "format": { | 6005 | "format": { |
| 5442 | "alignment": null, | 6006 | "alignment": null, |
| 5443 | "bold": null, | 6007 | "bold": null, |
| ... | @@ -5454,6 +6018,7 @@ | ... | @@ -5454,6 +6018,7 @@ |
| 5454 | "line_spacing": null, | 6018 | "line_spacing": null, |
| 5455 | "list_spacing": null, | 6019 | "list_spacing": null, |
| 5456 | "math": null, | 6020 | "math": null, |
| 6021 | "math_object": null, | ||
| 5457 | "rtl": null, | 6022 | "rtl": null, |
| 5458 | "space_after": null, | 6023 | "space_after": null, |
| 5459 | "space_before": null, | 6024 | "space_before": null, |
| ... | @@ -5468,6 +6033,24 @@ | ... | @@ -5468,6 +6033,24 @@ |
| 5468 | } | 6033 | } |
| 5469 | } | 6034 | } |
| 5470 | }, | 6035 | }, |
| 6036 | "identity": [ | ||
| 6037 | 175, | ||
| 6038 | 234, | ||
| 6039 | 69, | ||
| 6040 | 208, | ||
| 6041 | 233, | ||
| 6042 | 172, | ||
| 6043 | 79, | ||
| 6044 | 75, | ||
| 6045 | 151, | ||
| 6046 | 155, | ||
| 6047 | 227, | ||
| 6048 | 72, | ||
| 6049 | 164, | ||
| 6050 | 173, | ||
| 6051 | 230, | ||
| 6052 | 147 | ||
| 6053 | ], | ||
| 5471 | "margin_origin": [ | 6054 | "margin_origin": [ |
| 5472 | 0.0, | 6055 | 0.0, |
| 5473 | 0.0 | 6056 | 0.0 |
| ... | @@ -5476,7 +6059,7 @@ | ... | @@ -5476,7 +6059,7 @@ |
| 5476 | { | 6059 | { |
| 5477 | "Title": { | 6060 | "Title": { |
| 5478 | "date": null, | 6061 | "date": null, |
| 5479 | "id": "{D2197C55-D962-4DF0-8B14-26F88C06D6FA},13", | 6062 | "id": "{D045EAAF-ACE9-4B4F-979B-E348A4ADE693},13", |
| 5480 | "layout": { | 6063 | "layout": { |
| 5481 | "max_height": null, | 6064 | "max_height": null, |
| 5482 | "max_width": null, | 6065 | "max_width": null, |
| ... | @@ -5487,24 +6070,29 @@ | ... | @@ -5487,24 +6070,29 @@ |
| 5487 | }, | 6070 | }, |
| 5488 | "outlines": [ | 6071 | "outlines": [ |
| 5489 | { | 6072 | { |
| 5490 | "id": "{D2197C55-D962-4DF0-8B14-26F88C06D6FA},14", | 6073 | "id": "{D045EAAF-ACE9-4B4F-979B-E348A4ADE693},14", |
| 5491 | "indents": [], | 6074 | "indents": [ |
| 6075 | 18.0, | ||
| 6076 | 0.0, | ||
| 6077 | 27.0, | ||
| 6078 | 27.0 | ||
| 6079 | ], | ||
| 5492 | "layout": { | 6080 | "layout": { |
| 5493 | "max_height": null, | 6081 | "max_height": 21.600000381469727, |
| 5494 | "max_width": null, | 6082 | "max_width": null, |
| 5495 | "reserved_width": null, | 6083 | "reserved_width": null, |
| 5496 | "width_set_by_user": null, | 6084 | "width_set_by_user": null, |
| 5497 | "x": null, | 6085 | "x": null, |
| 5498 | "y": null | 6086 | "y": null |
| 5499 | }, | 6087 | }, |
| 5500 | "min_width": null, | 6088 | "min_width": 162.0, |
| 5501 | "paragraphs": [ | 6089 | "paragraphs": [ |
| 5502 | { | 6090 | { |
| 5503 | "collapsed": false, | 6091 | "collapsed": false, |
| 5504 | "content": { | 6092 | "content": { |
| 5505 | "Text": { | 6093 | "Text": { |
| 5506 | "date_field": null, | 6094 | "date_field": null, |
| 5507 | "id": "{D2197C55-D962-4DF0-8B14-26F88C06D6FA},16", | 6095 | "id": "{D045EAAF-ACE9-4B4F-979B-E348A4ADE693},16", |
| 5508 | "tags": [], | 6096 | "tags": [], |
| 5509 | "text": { | 6097 | "text": { |
| 5510 | "spans": [ | 6098 | "spans": [ |
| ... | @@ -5526,6 +6114,7 @@ | ... | @@ -5526,6 +6114,7 @@ |
| 5526 | "line_spacing": null, | 6114 | "line_spacing": null, |
| 5527 | "list_spacing": null, | 6115 | "list_spacing": null, |
| 5528 | "math": null, | 6116 | "math": null, |
| 6117 | "math_object": null, | ||
| 5529 | "rtl": null, | 6118 | "rtl": null, |
| 5530 | "space_after": null, | 6119 | "space_after": null, |
| 5531 | "space_before": null, | 6120 | "space_before": null, |
| ... | @@ -5556,6 +6145,7 @@ | ... | @@ -5556,6 +6145,7 @@ |
| 5556 | "line_spacing": null, | 6145 | "line_spacing": null, |
| 5557 | "list_spacing": null, | 6146 | "list_spacing": null, |
| 5558 | "math": null, | 6147 | "math": null, |
| 6148 | "math_object": null, | ||
| 5559 | "rtl": null, | 6149 | "rtl": null, |
| 5560 | "space_after": null, | 6150 | "space_after": null, |
| 5561 | "space_before": null, | 6151 | "space_before": null, |
| ... | @@ -5564,15 +6154,19 @@ | ... | @@ -5564,15 +6154,19 @@ |
| 5564 | "superscript": null, | 6154 | "superscript": null, |
| 5565 | "underline": null | 6155 | "underline": null |
| 5566 | }, | 6156 | }, |
| 5567 | "id": "{D2197C55-D962-4DF0-8B14-26F88C06D6FA},15", | 6157 | "id": "{D045EAAF-ACE9-4B4F-979B-E348A4ADE693},15", |
| 5568 | "level": 1, | 6158 | "level": 1, |
| 5569 | "lists": [], | 6159 | "lists": [], |
| 6160 | "media": { | ||
| 6161 | "recordings": [], | ||
| 6162 | "time_ms": null | ||
| 6163 | }, | ||
| 5570 | "parent": null, | 6164 | "parent": null, |
| 5571 | "style": "{D2197C55-D962-4DF0-8B14-26F88C06D6FA},18", | 6165 | "style": "{D045EAAF-ACE9-4B4F-979B-E348A4ADE693},18", |
| 5572 | "tags": [] | 6166 | "tags": [] |
| 5573 | } | 6167 | } |
| 5574 | ], | 6168 | ], |
| 5575 | "title": false, | 6169 | "title": true, |
| 5576 | "unsupported": [] | 6170 | "unsupported": [] |
| 5577 | } | 6171 | } |
| 5578 | ] | 6172 | ] |
| ... | @@ -5583,7 +6177,7 @@ | ... | @@ -5583,7 +6177,7 @@ |
| 5583 | } | 6177 | } |
| 5584 | } | 6178 | } |
| 5585 | }, | 6179 | }, |
| 5586 | "space": "{D2197C55-D962-4DF0-8B14-26F88C06D6FA},1" | 6180 | "space": "{D045EAAF-ACE9-4B4F-979B-E348A4ADE693},1" |
| 5587 | } | 6181 | } |
| 5588 | ], | 6182 | ], |
| 5589 | [ | 6183 | [ |
| ... | @@ -5594,215 +6188,215 @@ | ... | @@ -5594,215 +6188,215 @@ |
| 5594 | "edits": [ | 6188 | "edits": [ |
| 5595 | { | 6189 | { |
| 5596 | "guid": [ | 6190 | "guid": [ |
| 5597 | 99, | ||
| 5598 | 82, | ||
| 5599 | 79, | ||
| 5600 | 67, | ||
| 5601 | 136, | ||
| 5602 | 178, | ||
| 5603 | 110, | ||
| 5604 | 79, | ||
| 5605 | 138, | ||
| 5606 | 9, | 6191 | 9, |
| 5607 | 172, | ||
| 5608 | 171, | ||
| 5609 | 67, | ||
| 5610 | 243, | 6192 | 243, |
| 5611 | 71, | 6193 | 107, |
| 5612 | 142 | 6194 | 19, |
| 6195 | 20, | ||
| 6196 | 192, | ||
| 6197 | 128, | ||
| 6198 | 78, | ||
| 6199 | 179, | ||
| 6200 | 30, | ||
| 6201 | 140, | ||
| 6202 | 253, | ||
| 6203 | 10, | ||
| 6204 | 214, | ||
| 6205 | 221, | ||
| 6206 | 155 | ||
| 5613 | ], | 6207 | ], |
| 5614 | "level": 1, | 6208 | "level": 1, |
| 5615 | "position": { | 6209 | "position": { |
| 5616 | "Before": "{F2D6AEBB-98EA-4F10-991F-5243E74AF450},1" | 6210 | "Before": "{D63FB383-FB37-44F9-AEBB-E868E903A2BB},1" |
| 5617 | }, | 6211 | }, |
| 5618 | "space": "{406687D7-F9E9-491B-85DA-166DE6CD5A60},1" | 6212 | "space": "{C6E54364-6CD0-4764-8180-873030B9EA53},1" |
| 5619 | }, | 6213 | }, |
| 5620 | { | 6214 | { |
| 5621 | "guid": [ | 6215 | "guid": [ |
| 5622 | 198, | 6216 | 9, |
| 5623 | 58, | 6217 | 192, |
| 5624 | 35, | 6218 | 29, |
| 5625 | 75, | 6219 | 108, |
| 5626 | 157, | 6220 | 238, |
| 5627 | 18, | 6221 | 78, |
| 5628 | 195, | 6222 | 236, |
| 5629 | 77, | 6223 | 72, |
| 5630 | 154, | 6224 | 128, |
| 5631 | 80, | 6225 | 200, |
| 5632 | 148, | 6226 | 112, |
| 5633 | 1, | 6227 | 224, |
| 5634 | 210, | 6228 | 246, |
| 5635 | 118, | 6229 | 115, |
| 5636 | 195, | 6230 | 92, |
| 5637 | 115 | 6231 | 133 |
| 5638 | ], | 6232 | ], |
| 5639 | "level": 2, | 6233 | "level": 2, |
| 5640 | "position": "Keep", | 6234 | "position": "Keep", |
| 5641 | "space": "{1F73BF36-9FC2-495D-844D-B370D5C47DE7},1" | 6235 | "space": "{DEEC391B-F2D1-4EB1-BEB1-57598DFBB982},1" |
| 5642 | } | 6236 | } |
| 5643 | ], | 6237 | ], |
| 5644 | "observed": [ | 6238 | "observed": [ |
| 5645 | [ | 6239 | [ |
| 5646 | "{869D5929-6E5E-4EDB-9639-4C56977DD447},1", | 6240 | "{0B37E922-551A-48F8-BCAC-4AB3503B4D28},1", |
| 5647 | 1 | 6241 | 1 |
| 5648 | ], | 6242 | ], |
| 5649 | [ | 6243 | [ |
| 5650 | "{B0B53F94-0105-4C59-B65B-9473F49BFF41},1", | 6244 | "{74492C4A-164B-40E9-B297-4D3B4685347D},1", |
| 5651 | 1 | 6245 | 1 |
| 5652 | ], | 6246 | ], |
| 5653 | [ | 6247 | [ |
| 5654 | "{0E98EE82-B78F-4872-8BD9-5D9B23A817C5},1", | 6248 | "{DAFF533D-608B-4735-BA1D-2354E8CB8A69},1", |
| 5655 | 1 | 6249 | 1 |
| 5656 | ], | 6250 | ], |
| 5657 | [ | 6251 | [ |
| 5658 | "{C837A033-F055-4455-B45F-17D82016921C},1", | 6252 | "{8B766F11-2A4F-4D95-8473-D3CA68129E11},1", |
| 5659 | 1 | 6253 | 1 |
| 5660 | ], | 6254 | ], |
| 5661 | [ | 6255 | [ |
| 5662 | "{89D86121-BF5C-418B-BB92-CBFFA8EF545F},1", | 6256 | "{28E0CC5F-68BC-4A86-9ED7-114FFD3BAE9A},1", |
| 5663 | 1 | 6257 | 1 |
| 5664 | ], | 6258 | ], |
| 5665 | [ | 6259 | [ |
| 5666 | "{57E835EA-2799-4118-B55C-7BB1E51C6941},1", | 6260 | "{31FFA5F9-C1B8-412D-A2D6-7ACC3CC16FD0},1", |
| 5667 | 1 | 6261 | 1 |
| 5668 | ], | 6262 | ], |
| 5669 | [ | 6263 | [ |
| 5670 | "{F7EECCCF-B6B4-44F2-A543-36837EC18B8A},1", | 6264 | "{AFCD09F6-1085-4582-BDB2-4B91E15D2088},1", |
| 5671 | 1 | 6265 | 1 |
| 5672 | ], | 6266 | ], |
| 5673 | [ | 6267 | [ |
| 5674 | "{361A0F09-EA63-4F2D-9620-C061D4E4707E},1", | 6268 | "{A94D5882-C902-4CAE-B38E-2ED56EEF177F},1", |
| 5675 | 1 | 6269 | 1 |
| 5676 | ], | 6270 | ], |
| 5677 | [ | 6271 | [ |
| 5678 | "{EB3940F1-F255-42BE-8C31-DD09E86DBAAF},1", | 6272 | "{5AB37A0F-5702-4C8E-A8C6-79ED6ED1E69F},1", |
| 5679 | 1 | 6273 | 1 |
| 5680 | ], | 6274 | ], |
| 5681 | [ | 6275 | [ |
| 5682 | "{C2870D07-8ADC-44BB-9F12-15D6C3708689},1", | 6276 | "{F212795E-0E4B-4844-A8A6-ABD7F7025124},1", |
| 5683 | 1 | 6277 | 1 |
| 5684 | ], | 6278 | ], |
| 5685 | [ | 6279 | [ |
| 5686 | "{1E0C6331-32C9-4EF9-8087-150A8669FA91},1", | 6280 | "{1786F2CA-0A43-4081-8B55-B207EEE0331C},1", |
| 5687 | 1 | 6281 | 1 |
| 5688 | ], | 6282 | ], |
| 5689 | [ | 6283 | [ |
| 5690 | "{ED9769DD-99D6-4BE2-B980-580DA9C4DFCE},1", | 6284 | "{3E966EF8-B7B4-483F-B241-8F21DF318AB3},1", |
| 5691 | 1 | 6285 | 1 |
| 5692 | ], | 6286 | ], |
| 5693 | [ | 6287 | [ |
| 5694 | "{17C30054-7129-4A10-9D00-4D5545E4F4BA},1", | 6288 | "{0D361C76-92FD-4B76-901B-809C2C3C3CE6},1", |
| 5695 | 1 | 6289 | 1 |
| 5696 | ], | 6290 | ], |
| 5697 | [ | 6291 | [ |
| 5698 | "{4C5FBA5B-E348-4750-ACE4-E9694D527BFF},1", | 6292 | "{AE89305A-73F4-46B5-B41D-18884685D896},1", |
| 5699 | 1 | 6293 | 1 |
| 5700 | ], | 6294 | ], |
| 5701 | [ | 6295 | [ |
| 5702 | "{BD0EF658-4D0F-42A7-B546-B4F4A77D246B},1", | 6296 | "{551D2637-2AA7-4923-B2DD-31C2E1489FD2},1", |
| 5703 | 1 | 6297 | 1 |
| 5704 | ], | 6298 | ], |
| 5705 | [ | 6299 | [ |
| 5706 | "{6FEA70FA-1451-4A27-8A8E-C4CC3B713CF6},1", | 6300 | "{CBF82C86-3A8B-457F-92A4-1AC7BF436CE9},1", |
| 5707 | 1 | 6301 | 1 |
| 5708 | ], | 6302 | ], |
| 5709 | [ | 6303 | [ |
| 5710 | "{68A1E0E1-7A39-4AB6-AD71-4D8A18878D79},1", | 6304 | "{42010094-E377-4A97-9395-AD45808D0552},1", |
| 5711 | 1 | 6305 | 1 |
| 5712 | ], | 6306 | ], |
| 5713 | [ | 6307 | [ |
| 5714 | "{7E9041BD-301B-4B5B-9456-1C92050E4914},1", | 6308 | "{0F6ABB5E-A021-4E1D-B187-7A72E74000DC},1", |
| 5715 | 1 | 6309 | 1 |
| 5716 | ], | 6310 | ], |
| 5717 | [ | 6311 | [ |
| 5718 | "{EFA77BAE-8A98-4176-B536-B615D1D2ED2B},1", | 6312 | "{F9DA3139-5856-4B11-95C0-356683E3ACFE},1", |
| 5719 | 1 | 6313 | 1 |
| 5720 | ], | 6314 | ], |
| 5721 | [ | 6315 | [ |
| 5722 | "{87AC0239-51A5-47C6-BAF4-926CCF56385C},1", | 6316 | "{439B80E0-9D4C-4A2F-9163-4E301491364D},1", |
| 5723 | 1 | 6317 | 1 |
| 5724 | ], | 6318 | ], |
| 5725 | [ | 6319 | [ |
| 5726 | "{C50BF7FC-F848-4B91-B8C8-743D0AA5F195},1", | 6320 | "{B03B973C-50BC-4B9A-9914-B1D5C0CF6197},1", |
| 5727 | 1 | 6321 | 1 |
| 5728 | ], | 6322 | ], |
| 5729 | [ | 6323 | [ |
| 5730 | "{FD8D07D0-C447-4516-A7DB-F8522B77D4A7},1", | 6324 | "{A459C474-E6EB-4BE5-9193-271799B2860B},1", |
| 5731 | 1 | 6325 | 1 |
| 5732 | ], | 6326 | ], |
| 5733 | [ | 6327 | [ |
| 5734 | "{D2E1FE57-F45D-49C3-B388-95222ACA3886},1", | 6328 | "{4ED14CEE-8560-4125-B488-9945C116741E},1", |
| 5735 | 1 | 6329 | 1 |
| 5736 | ], | 6330 | ], |
| 5737 | [ | 6331 | [ |
| 5738 | "{7F6702B3-C977-436F-82AF-403E2BE950E7},1", | 6332 | "{C4FF8FE4-4C43-461F-98DD-EB1FF2A863B2},1", |
| 5739 | 1 | 6333 | 1 |
| 5740 | ], | 6334 | ], |
| 5741 | [ | 6335 | [ |
| 5742 | "{688C4EA1-80F5-4D85-B9BB-F8633FF3C0CA},1", | 6336 | "{932BE37F-05AA-4EFB-85D8-A33445E738B3},1", |
| 5743 | 1 | 6337 | 1 |
| 5744 | ], | 6338 | ], |
| 5745 | [ | 6339 | [ |
| 5746 | "{754FA957-C432-4502-A5CF-EE44449462C0},1", | 6340 | "{9B755368-1D49-4D7E-8D33-6DC536F57848},1", |
| 5747 | 1 | 6341 | 1 |
| 5748 | ], | 6342 | ], |
| 5749 | [ | 6343 | [ |
| 5750 | "{D2197C55-D962-4DF0-8B14-26F88C06D6FA},1", | 6344 | "{D045EAAF-ACE9-4B4F-979B-E348A4ADE693},1", |
| 5751 | 1 | 6345 | 1 |
| 5752 | ], | 6346 | ], |
| 5753 | [ | 6347 | [ |
| 5754 | "{87B83BA8-A906-4F71-A6CA-3E8133A75D34},1", | 6348 | "{5CA4C59F-B400-4BC6-8B90-30ECAC35CB43},1", |
| 5755 | 1 | 6349 | 1 |
| 5756 | ], | 6350 | ], |
| 5757 | [ | 6351 | [ |
| 5758 | "{F2D6AEBB-98EA-4F10-991F-5243E74AF450},1", | 6352 | "{D63FB383-FB37-44F9-AEBB-E868E903A2BB},1", |
| 5759 | 1 | 6353 | 1 |
| 5760 | ], | 6354 | ], |
| 5761 | [ | 6355 | [ |
| 5762 | "{406687D7-F9E9-491B-85DA-166DE6CD5A60},1", | 6356 | "{C6E54364-6CD0-4764-8180-873030B9EA53},1", |
| 5763 | 1 | 6357 | 1 |
| 5764 | ], | 6358 | ], |
| 5765 | [ | 6359 | [ |
| 5766 | "{1F73BF36-9FC2-495D-844D-B370D5C47DE7},1", | 6360 | "{DEEC391B-F2D1-4EB1-BEB1-57598DFBB982},1", |
| 5767 | 1 | 6361 | 1 |
| 5768 | ], | 6362 | ], |
| 5769 | [ | 6363 | [ |
| 5770 | "{E9BD343F-4A66-4DE2-81F9-3789CCA22818},1", | 6364 | "{9E7616ED-DE19-4625-8EA0-0C5DEB18B690},1", |
| 5771 | 1 | 6365 | 1 |
| 5772 | ], | 6366 | ], |
| 5773 | [ | 6367 | [ |
| 5774 | "{189EA744-BE13-467A-95BA-2667A134C0B0},1", | 6368 | "{89F1748D-6749-4AD0-BAF6-07E9F9C058E5},1", |
| 5775 | 1 | 6369 | 1 |
| 5776 | ], | 6370 | ], |
| 5777 | [ | 6371 | [ |
| 5778 | "{017F5881-F818-4C7C-B9F9-6BCB4085F1D0},1", | 6372 | "{00B10C58-6649-428D-A081-3EBBA6E88E97},1", |
| 5779 | 1 | 6373 | 1 |
| 5780 | ], | 6374 | ], |
| 5781 | [ | 6375 | [ |
| 5782 | "{CD0D17F4-25D8-4076-9AF7-47D9CDEFCF0A},1", | 6376 | "{58E99482-710A-4938-8CFB-88A1C9B946C9},1", |
| 5783 | 1 | 6377 | 1 |
| 5784 | ], | 6378 | ], |
| 5785 | [ | 6379 | [ |
| 5786 | "{CCBB5969-BCE8-498B-BE1A-98FD3A44DB1A},1", | 6380 | "{F721C9D5-9A6A-405F-94C9-9B85A7EEB980},1", |
| 5787 | 1 | 6381 | 1 |
| 5788 | ], | 6382 | ], |
| 5789 | [ | 6383 | [ |
| 5790 | "{2B736B19-2D9C-4E76-A732-478D533D717A},1", | 6384 | "{15A45C01-A9E8-42E2-A267-C7FCE728815E},1", |
| 5791 | 1 | 6385 | 1 |
| 5792 | ] | 6386 | ] |
| 5793 | ] | 6387 | ] |
| 5794 | } | 6388 | } |
| 5795 | }, | 6389 | }, |
| 5796 | "space": "{90FCDB0C-36AE-422F-B4FE-67676826FE36},1" | 6390 | "space": "{A5A2AFBE-1DF1-4190-AAC7-1AD29C9564F2},1" |
| 5797 | }, | 6391 | }, |
| 5798 | { | 6392 | { |
| 5799 | "id": 2, | 6393 | "id": 2, |
| 5800 | "operation": { | 6394 | "operation": { |
| 5801 | "Page": { | 6395 | "Page": { |
| 5802 | "after": { | 6396 | "after": { |
| 5803 | "created": 134335976560000000, | 6397 | "created": 134348779900000000, |
| 5804 | "definitions": { | 6398 | "definitions": { |
| 5805 | "{406687D7-F9E9-491B-85DA-166DE6CD5A60},18": { | 6399 | "{C6E54364-6CD0-4764-8180-873030B9EA53},18": { |
| 5806 | "format": { | 6400 | "format": { |
| 5807 | "alignment": null, | 6401 | "alignment": null, |
| 5808 | "bold": null, | 6402 | "bold": null, |
| ... | @@ -5819,6 +6413,7 @@ | ... | @@ -5819,6 +6413,7 @@ |
| 5819 | "line_spacing": null, | 6413 | "line_spacing": null, |
| 5820 | "list_spacing": null, | 6414 | "list_spacing": null, |
| 5821 | "math": null, | 6415 | "math": null, |
| 6416 | "math_object": null, | ||
| 5822 | "rtl": null, | 6417 | "rtl": null, |
| 5823 | "space_after": null, | 6418 | "space_after": null, |
| 5824 | "space_before": null, | 6419 | "space_before": null, |
| ... | @@ -5833,6 +6428,24 @@ | ... | @@ -5833,6 +6428,24 @@ |
| 5833 | } | 6428 | } |
| 5834 | } | 6429 | } |
| 5835 | }, | 6430 | }, |
| 6431 | "identity": [ | ||
| 6432 | 100, | ||
| 6433 | 67, | ||
| 6434 | 229, | ||
| 6435 | 198, | ||
| 6436 | 208, | ||
| 6437 | 108, | ||
| 6438 | 100, | ||
| 6439 | 71, | ||
| 6440 | 129, | ||
| 6441 | 128, | ||
| 6442 | 135, | ||
| 6443 | 48, | ||
| 6444 | 48, | ||
| 6445 | 185, | ||
| 6446 | 234, | ||
| 6447 | 83 | ||
| 6448 | ], | ||
| 5836 | "margin_origin": [ | 6449 | "margin_origin": [ |
| 5837 | 0.0, | 6450 | 0.0, |
| 5838 | 0.0 | 6451 | 0.0 |
| ... | @@ -5840,7 +6453,7 @@ | ... | @@ -5840,7 +6453,7 @@ |
| 5840 | "objects": [ | 6453 | "objects": [ |
| 5841 | { | 6454 | { |
| 5842 | "Outline": { | 6455 | "Outline": { |
| 5843 | "id": "{69906E37-5C35-4F2F-B74F-B5D45950739B},0", | 6456 | "id": "{91379CE6-91DA-48BA-B306-77E4CEB068B6},29", |
| 5844 | "indents": [], | 6457 | "indents": [], |
| 5845 | "layout": { | 6458 | "layout": { |
| 5846 | "max_height": null, | 6459 | "max_height": null, |
| ... | @@ -5857,7 +6470,7 @@ | ... | @@ -5857,7 +6470,7 @@ |
| 5857 | "content": { | 6470 | "content": { |
| 5858 | "Text": { | 6471 | "Text": { |
| 5859 | "date_field": null, | 6472 | "date_field": null, |
| 5860 | "id": "{3A6E4FCA-F6C5-4418-ADF4-9E92782F26CD},0", | 6473 | "id": "{91379CE6-91DA-48BA-B306-77E4CEB068B6},28", |
| 5861 | "tags": [], | 6474 | "tags": [], |
| 5862 | "text": { | 6475 | "text": { |
| 5863 | "spans": [ | 6476 | "spans": [ |
| ... | @@ -5879,6 +6492,7 @@ | ... | @@ -5879,6 +6492,7 @@ |
| 5879 | "line_spacing": null, | 6492 | "line_spacing": null, |
| 5880 | "list_spacing": null, | 6493 | "list_spacing": null, |
| 5881 | "math": null, | 6494 | "math": null, |
| 6495 | "math_object": null, | ||
| 5882 | "rtl": null, | 6496 | "rtl": null, |
| 5883 | "space_after": null, | 6497 | "space_after": null, |
| 5884 | "space_before": null, | 6498 | "space_before": null, |
| ... | @@ -5909,6 +6523,7 @@ | ... | @@ -5909,6 +6523,7 @@ |
| 5909 | "line_spacing": null, | 6523 | "line_spacing": null, |
| 5910 | "list_spacing": null, | 6524 | "list_spacing": null, |
| 5911 | "math": null, | 6525 | "math": null, |
| 6526 | "math_object": null, | ||
| 5912 | "rtl": null, | 6527 | "rtl": null, |
| 5913 | "space_after": null, | 6528 | "space_after": null, |
| 5914 | "space_before": null, | 6529 | "space_before": null, |
| ... | @@ -5917,9 +6532,13 @@ | ... | @@ -5917,9 +6532,13 @@ |
| 5917 | "superscript": null, | 6532 | "superscript": null, |
| 5918 | "underline": null | 6533 | "underline": null |
| 5919 | }, | 6534 | }, |
| 5920 | "id": "{FFE94E14-C2A1-4CB1-BBDB-0D72059C0A9D},0", | 6535 | "id": "{91379CE6-91DA-48BA-B306-77E4CEB068B6},30", |
| 5921 | "level": 1, | 6536 | "level": 1, |
| 5922 | "lists": [], | 6537 | "lists": [], |
| 6538 | "media": { | ||
| 6539 | "recordings": [], | ||
| 6540 | "time_ms": null | ||
| 6541 | }, | ||
| 5923 | "parent": null, | 6542 | "parent": null, |
| 5924 | "style": null, | 6543 | "style": null, |
| 5925 | "tags": [] | 6544 | "tags": [] |
| ... | @@ -5932,7 +6551,7 @@ | ... | @@ -5932,7 +6551,7 @@ |
| 5932 | { | 6551 | { |
| 5933 | "Title": { | 6552 | "Title": { |
| 5934 | "date": null, | 6553 | "date": null, |
| 5935 | "id": "{406687D7-F9E9-491B-85DA-166DE6CD5A60},13", | 6554 | "id": "{C6E54364-6CD0-4764-8180-873030B9EA53},13", |
| 5936 | "layout": { | 6555 | "layout": { |
| 5937 | "max_height": null, | 6556 | "max_height": null, |
| 5938 | "max_width": null, | 6557 | "max_width": null, |
| ... | @@ -5943,24 +6562,29 @@ | ... | @@ -5943,24 +6562,29 @@ |
| 5943 | }, | 6562 | }, |
| 5944 | "outlines": [ | 6563 | "outlines": [ |
| 5945 | { | 6564 | { |
| 5946 | "id": "{406687D7-F9E9-491B-85DA-166DE6CD5A60},14", | 6565 | "id": "{C6E54364-6CD0-4764-8180-873030B9EA53},14", |
| 5947 | "indents": [], | 6566 | "indents": [ |
| 6567 | 18.0, | ||
| 6568 | 0.0, | ||
| 6569 | 27.0, | ||
| 6570 | 27.0 | ||
| 6571 | ], | ||
| 5948 | "layout": { | 6572 | "layout": { |
| 5949 | "max_height": null, | 6573 | "max_height": 21.600000381469727, |
| 5950 | "max_width": null, | 6574 | "max_width": null, |
| 5951 | "reserved_width": null, | 6575 | "reserved_width": null, |
| 5952 | "width_set_by_user": null, | 6576 | "width_set_by_user": null, |
| 5953 | "x": null, | 6577 | "x": null, |
| 5954 | "y": null | 6578 | "y": null |
| 5955 | }, | 6579 | }, |
| 5956 | "min_width": null, | 6580 | "min_width": 162.0, |
| 5957 | "paragraphs": [ | 6581 | "paragraphs": [ |
| 5958 | { | 6582 | { |
| 5959 | "collapsed": false, | 6583 | "collapsed": false, |
| 5960 | "content": { | 6584 | "content": { |
| 5961 | "Text": { | 6585 | "Text": { |
| 5962 | "date_field": null, | 6586 | "date_field": null, |
| 5963 | "id": "{406687D7-F9E9-491B-85DA-166DE6CD5A60},16", | 6587 | "id": "{C6E54364-6CD0-4764-8180-873030B9EA53},16", |
| 5964 | "tags": [], | 6588 | "tags": [], |
| 5965 | "text": { | 6589 | "text": { |
| 5966 | "spans": [ | 6590 | "spans": [ |
| ... | @@ -5982,6 +6606,7 @@ | ... | @@ -5982,6 +6606,7 @@ |
| 5982 | "line_spacing": null, | 6606 | "line_spacing": null, |
| 5983 | "list_spacing": null, | 6607 | "list_spacing": null, |
| 5984 | "math": null, | 6608 | "math": null, |
| 6609 | "math_object": null, | ||
| 5985 | "rtl": null, | 6610 | "rtl": null, |
| 5986 | "space_after": null, | 6611 | "space_after": null, |
| 5987 | "space_before": null, | 6612 | "space_before": null, |
| ... | @@ -6012,6 +6637,7 @@ | ... | @@ -6012,6 +6637,7 @@ |
| 6012 | "line_spacing": null, | 6637 | "line_spacing": null, |
| 6013 | "list_spacing": null, | 6638 | "list_spacing": null, |
| 6014 | "math": null, | 6639 | "math": null, |
| 6640 | "math_object": null, | ||
| 6015 | "rtl": null, | 6641 | "rtl": null, |
| 6016 | "space_after": null, | 6642 | "space_after": null, |
| 6017 | "space_before": null, | 6643 | "space_before": null, |
| ... | @@ -6020,15 +6646,19 @@ | ... | @@ -6020,15 +6646,19 @@ |
| 6020 | "superscript": null, | 6646 | "superscript": null, |
| 6021 | "underline": null | 6647 | "underline": null |
| 6022 | }, | 6648 | }, |
| 6023 | "id": "{406687D7-F9E9-491B-85DA-166DE6CD5A60},15", | 6649 | "id": "{C6E54364-6CD0-4764-8180-873030B9EA53},15", |
| 6024 | "level": 1, | 6650 | "level": 1, |
| 6025 | "lists": [], | 6651 | "lists": [], |
| 6652 | "media": { | ||
| 6653 | "recordings": [], | ||
| 6654 | "time_ms": null | ||
| 6655 | }, | ||
| 6026 | "parent": null, | 6656 | "parent": null, |
| 6027 | "style": "{406687D7-F9E9-491B-85DA-166DE6CD5A60},18", | 6657 | "style": "{C6E54364-6CD0-4764-8180-873030B9EA53},18", |
| 6028 | "tags": [] | 6658 | "tags": [] |
| 6029 | } | 6659 | } |
| 6030 | ], | 6660 | ], |
| 6031 | "title": false, | 6661 | "title": true, |
| 6032 | "unsupported": [] | 6662 | "unsupported": [] |
| 6033 | } | 6663 | } |
| 6034 | ] | 6664 | ] |
| ... | @@ -6039,9 +6669,9 @@ | ... | @@ -6039,9 +6669,9 @@ |
| 6039 | }, | 6669 | }, |
| 6040 | "author": "Author", | 6670 | "author": "Author", |
| 6041 | "before": { | 6671 | "before": { |
| 6042 | "created": 134335976560000000, | 6672 | "created": 134348779900000000, |
| 6043 | "definitions": { | 6673 | "definitions": { |
| 6044 | "{406687D7-F9E9-491B-85DA-166DE6CD5A60},18": { | 6674 | "{C6E54364-6CD0-4764-8180-873030B9EA53},18": { |
| 6045 | "format": { | 6675 | "format": { |
| 6046 | "alignment": null, | 6676 | "alignment": null, |
| 6047 | "bold": null, | 6677 | "bold": null, |
| ... | @@ -6058,6 +6688,7 @@ | ... | @@ -6058,6 +6688,7 @@ |
| 6058 | "line_spacing": null, | 6688 | "line_spacing": null, |
| 6059 | "list_spacing": null, | 6689 | "list_spacing": null, |
| 6060 | "math": null, | 6690 | "math": null, |
| 6691 | "math_object": null, | ||
| 6061 | "rtl": null, | 6692 | "rtl": null, |
| 6062 | "space_after": null, | 6693 | "space_after": null, |
| 6063 | "space_before": null, | 6694 | "space_before": null, |
| ... | @@ -6072,6 +6703,24 @@ | ... | @@ -6072,6 +6703,24 @@ |
| 6072 | } | 6703 | } |
| 6073 | } | 6704 | } |
| 6074 | }, | 6705 | }, |
| 6706 | "identity": [ | ||
| 6707 | 100, | ||
| 6708 | 67, | ||
| 6709 | 229, | ||
| 6710 | 198, | ||
| 6711 | 208, | ||
| 6712 | 108, | ||
| 6713 | 100, | ||
| 6714 | 71, | ||
| 6715 | 129, | ||
| 6716 | 128, | ||
| 6717 | 135, | ||
| 6718 | 48, | ||
| 6719 | 48, | ||
| 6720 | 185, | ||
| 6721 | 234, | ||
| 6722 | 83 | ||
| 6723 | ], | ||
| 6075 | "margin_origin": [ | 6724 | "margin_origin": [ |
| 6076 | 0.0, | 6725 | 0.0, |
| 6077 | 0.0 | 6726 | 0.0 |
| ... | @@ -6080,7 +6729,7 @@ | ... | @@ -6080,7 +6729,7 @@ |
| 6080 | { | 6729 | { |
| 6081 | "Title": { | 6730 | "Title": { |
| 6082 | "date": null, | 6731 | "date": null, |
| 6083 | "id": "{406687D7-F9E9-491B-85DA-166DE6CD5A60},13", | 6732 | "id": "{C6E54364-6CD0-4764-8180-873030B9EA53},13", |
| 6084 | "layout": { | 6733 | "layout": { |
| 6085 | "max_height": null, | 6734 | "max_height": null, |
| 6086 | "max_width": null, | 6735 | "max_width": null, |
| ... | @@ -6091,24 +6740,29 @@ | ... | @@ -6091,24 +6740,29 @@ |
| 6091 | }, | 6740 | }, |
| 6092 | "outlines": [ | 6741 | "outlines": [ |
| 6093 | { | 6742 | { |
| 6094 | "id": "{406687D7-F9E9-491B-85DA-166DE6CD5A60},14", | 6743 | "id": "{C6E54364-6CD0-4764-8180-873030B9EA53},14", |
| 6095 | "indents": [], | 6744 | "indents": [ |
| 6745 | 18.0, | ||
| 6746 | 0.0, | ||
| 6747 | 27.0, | ||
| 6748 | 27.0 | ||
| 6749 | ], | ||
| 6096 | "layout": { | 6750 | "layout": { |
| 6097 | "max_height": null, | 6751 | "max_height": 21.600000381469727, |
| 6098 | "max_width": null, | 6752 | "max_width": null, |
| 6099 | "reserved_width": null, | 6753 | "reserved_width": null, |
| 6100 | "width_set_by_user": null, | 6754 | "width_set_by_user": null, |
| 6101 | "x": null, | 6755 | "x": null, |
| 6102 | "y": null | 6756 | "y": null |
| 6103 | }, | 6757 | }, |
| 6104 | "min_width": null, | 6758 | "min_width": 162.0, |
| 6105 | "paragraphs": [ | 6759 | "paragraphs": [ |
| 6106 | { | 6760 | { |
| 6107 | "collapsed": false, | 6761 | "collapsed": false, |
| 6108 | "content": { | 6762 | "content": { |
| 6109 | "Text": { | 6763 | "Text": { |
| 6110 | "date_field": null, | 6764 | "date_field": null, |
| 6111 | "id": "{406687D7-F9E9-491B-85DA-166DE6CD5A60},16", | 6765 | "id": "{C6E54364-6CD0-4764-8180-873030B9EA53},16", |
| 6112 | "tags": [], | 6766 | "tags": [], |
| 6113 | "text": { | 6767 | "text": { |
| 6114 | "spans": [ | 6768 | "spans": [ |
| ... | @@ -6130,6 +6784,7 @@ | ... | @@ -6130,6 +6784,7 @@ |
| 6130 | "line_spacing": null, | 6784 | "line_spacing": null, |
| 6131 | "list_spacing": null, | 6785 | "list_spacing": null, |
| 6132 | "math": null, | 6786 | "math": null, |
| 6787 | "math_object": null, | ||
| 6133 | "rtl": null, | 6788 | "rtl": null, |
| 6134 | "space_after": null, | 6789 | "space_after": null, |
| 6135 | "space_before": null, | 6790 | "space_before": null, |
| ... | @@ -6160,6 +6815,7 @@ | ... | @@ -6160,6 +6815,7 @@ |
| 6160 | "line_spacing": null, | 6815 | "line_spacing": null, |
| 6161 | "list_spacing": null, | 6816 | "list_spacing": null, |
| 6162 | "math": null, | 6817 | "math": null, |
| 6818 | "math_object": null, | ||
| 6163 | "rtl": null, | 6819 | "rtl": null, |
| 6164 | "space_after": null, | 6820 | "space_after": null, |
| 6165 | "space_before": null, | 6821 | "space_before": null, |
| ... | @@ -6168,15 +6824,19 @@ | ... | @@ -6168,15 +6824,19 @@ |
| 6168 | "superscript": null, | 6824 | "superscript": null, |
| 6169 | "underline": null | 6825 | "underline": null |
| 6170 | }, | 6826 | }, |
| 6171 | "id": "{406687D7-F9E9-491B-85DA-166DE6CD5A60},15", | 6827 | "id": "{C6E54364-6CD0-4764-8180-873030B9EA53},15", |
| 6172 | "level": 1, | 6828 | "level": 1, |
| 6173 | "lists": [], | 6829 | "lists": [], |
| 6830 | "media": { | ||
| 6831 | "recordings": [], | ||
| 6832 | "time_ms": null | ||
| 6833 | }, | ||
| 6174 | "parent": null, | 6834 | "parent": null, |
| 6175 | "style": "{406687D7-F9E9-491B-85DA-166DE6CD5A60},18", | 6835 | "style": "{C6E54364-6CD0-4764-8180-873030B9EA53},18", |
| 6176 | "tags": [] | 6836 | "tags": [] |
| 6177 | } | 6837 | } |
| 6178 | ], | 6838 | ], |
| 6179 | "title": false, | 6839 | "title": true, |
| 6180 | "unsupported": [] | 6840 | "unsupported": [] |
| 6181 | } | 6841 | } |
| 6182 | ] | 6842 | ] |
| ... | @@ -6187,7 +6847,7 @@ | ... | @@ -6187,7 +6847,7 @@ |
| 6187 | } | 6847 | } |
| 6188 | } | 6848 | } |
| 6189 | }, | 6849 | }, |
| 6190 | "space": "{406687D7-F9E9-491B-85DA-166DE6CD5A60},1" | 6850 | "space": "{C6E54364-6CD0-4764-8180-873030B9EA53},1" |
| 6191 | } | 6851 | } |
| 6192 | ], | 6852 | ], |
| 6193 | [ | 6853 | [ |
| ... | @@ -6198,215 +6858,215 @@ | ... | @@ -6198,215 +6858,215 @@ |
| 6198 | "edits": [ | 6858 | "edits": [ |
| 6199 | { | 6859 | { |
| 6200 | "guid": [ | 6860 | "guid": [ |
| 6201 | 99, | 6861 | 223, |
| 6202 | 113, | 6862 | 187, |
| 6203 | 79, | ||
| 6204 | 243, | ||
| 6205 | 201, | ||
| 6206 | 17, | 6863 | 17, |
| 6207 | 203, | 6864 | 5, |
| 6208 | 78, | 6865 | 4, |
| 6209 | 185, | 6866 | 237, |
| 6210 | 122, | 6867 | 130, |
| 6211 | 45, | 6868 | 69, |
| 6212 | 83, | 6869 | 161, |
| 6213 | 216, | 6870 | 20, |
| 6214 | 140, | 6871 | 5, |
| 6215 | 115, | 6872 | 179, |
| 6216 | 52 | 6873 | 161, |
| 6874 | 87, | ||
| 6875 | 94, | ||
| 6876 | 148 | ||
| 6217 | ], | 6877 | ], |
| 6218 | "level": 1, | 6878 | "level": 1, |
| 6219 | "position": { | 6879 | "position": { |
| 6220 | "Before": "{E9BD343F-4A66-4DE2-81F9-3789CCA22818},1" | 6880 | "Before": "{9E7616ED-DE19-4625-8EA0-0C5DEB18B690},1" |
| 6221 | }, | 6881 | }, |
| 6222 | "space": "{189EA744-BE13-467A-95BA-2667A134C0B0},1" | 6882 | "space": "{89F1748D-6749-4AD0-BAF6-07E9F9C058E5},1" |
| 6223 | }, | 6883 | }, |
| 6224 | { | 6884 | { |
| 6225 | "guid": [ | 6885 | "guid": [ |
| 6226 | 240, | 6886 | 219, |
| 6227 | 160, | 6887 | 20, |
| 6228 | 93, | 6888 | 156, |
| 6229 | 114, | 6889 | 115, |
| 6230 | 5, | 6890 | 106, |
| 6231 | 25, | 6891 | 105, |
| 6232 | 128, | 6892 | 134, |
| 6233 | 75, | 6893 | 64, |
| 6234 | 161, | 6894 | 131, |
| 6235 | 252, | 6895 | 56, |
| 6236 | 39, | 6896 | 1, |
| 6237 | 123, | 6897 | 213, |
| 6238 | 203, | 6898 | 21, |
| 6239 | 137, | 6899 | 147, |
| 6240 | 98, | 6900 | 139, |
| 6241 | 106 | 6901 | 107 |
| 6242 | ], | 6902 | ], |
| 6243 | "level": 2, | 6903 | "level": 2, |
| 6244 | "position": "Keep", | 6904 | "position": "Keep", |
| 6245 | "space": "{017F5881-F818-4C7C-B9F9-6BCB4085F1D0},1" | 6905 | "space": "{00B10C58-6649-428D-A081-3EBBA6E88E97},1" |
| 6246 | } | 6906 | } |
| 6247 | ], | 6907 | ], |
| 6248 | "observed": [ | 6908 | "observed": [ |
| 6249 | [ | 6909 | [ |
| 6250 | "{869D5929-6E5E-4EDB-9639-4C56977DD447},1", | 6910 | "{0B37E922-551A-48F8-BCAC-4AB3503B4D28},1", |
| 6251 | 1 | 6911 | 1 |
| 6252 | ], | 6912 | ], |
| 6253 | [ | 6913 | [ |
| 6254 | "{B0B53F94-0105-4C59-B65B-9473F49BFF41},1", | 6914 | "{74492C4A-164B-40E9-B297-4D3B4685347D},1", |
| 6255 | 1 | 6915 | 1 |
| 6256 | ], | 6916 | ], |
| 6257 | [ | 6917 | [ |
| 6258 | "{0E98EE82-B78F-4872-8BD9-5D9B23A817C5},1", | 6918 | "{DAFF533D-608B-4735-BA1D-2354E8CB8A69},1", |
| 6259 | 1 | 6919 | 1 |
| 6260 | ], | 6920 | ], |
| 6261 | [ | 6921 | [ |
| 6262 | "{C837A033-F055-4455-B45F-17D82016921C},1", | 6922 | "{8B766F11-2A4F-4D95-8473-D3CA68129E11},1", |
| 6263 | 1 | 6923 | 1 |
| 6264 | ], | 6924 | ], |
| 6265 | [ | 6925 | [ |
| 6266 | "{89D86121-BF5C-418B-BB92-CBFFA8EF545F},1", | 6926 | "{28E0CC5F-68BC-4A86-9ED7-114FFD3BAE9A},1", |
| 6267 | 1 | 6927 | 1 |
| 6268 | ], | 6928 | ], |
| 6269 | [ | 6929 | [ |
| 6270 | "{57E835EA-2799-4118-B55C-7BB1E51C6941},1", | 6930 | "{31FFA5F9-C1B8-412D-A2D6-7ACC3CC16FD0},1", |
| 6271 | 1 | 6931 | 1 |
| 6272 | ], | 6932 | ], |
| 6273 | [ | 6933 | [ |
| 6274 | "{F7EECCCF-B6B4-44F2-A543-36837EC18B8A},1", | 6934 | "{AFCD09F6-1085-4582-BDB2-4B91E15D2088},1", |
| 6275 | 1 | 6935 | 1 |
| 6276 | ], | 6936 | ], |
| 6277 | [ | 6937 | [ |
| 6278 | "{361A0F09-EA63-4F2D-9620-C061D4E4707E},1", | 6938 | "{A94D5882-C902-4CAE-B38E-2ED56EEF177F},1", |
| 6279 | 1 | 6939 | 1 |
| 6280 | ], | 6940 | ], |
| 6281 | [ | 6941 | [ |
| 6282 | "{EB3940F1-F255-42BE-8C31-DD09E86DBAAF},1", | 6942 | "{5AB37A0F-5702-4C8E-A8C6-79ED6ED1E69F},1", |
| 6283 | 1 | 6943 | 1 |
| 6284 | ], | 6944 | ], |
| 6285 | [ | 6945 | [ |
| 6286 | "{C2870D07-8ADC-44BB-9F12-15D6C3708689},1", | 6946 | "{F212795E-0E4B-4844-A8A6-ABD7F7025124},1", |
| 6287 | 1 | 6947 | 1 |
| 6288 | ], | 6948 | ], |
| 6289 | [ | 6949 | [ |
| 6290 | "{1E0C6331-32C9-4EF9-8087-150A8669FA91},1", | 6950 | "{1786F2CA-0A43-4081-8B55-B207EEE0331C},1", |
| 6291 | 1 | 6951 | 1 |
| 6292 | ], | 6952 | ], |
| 6293 | [ | 6953 | [ |
| 6294 | "{ED9769DD-99D6-4BE2-B980-580DA9C4DFCE},1", | 6954 | "{3E966EF8-B7B4-483F-B241-8F21DF318AB3},1", |
| 6295 | 1 | 6955 | 1 |
| 6296 | ], | 6956 | ], |
| 6297 | [ | 6957 | [ |
| 6298 | "{17C30054-7129-4A10-9D00-4D5545E4F4BA},1", | 6958 | "{0D361C76-92FD-4B76-901B-809C2C3C3CE6},1", |
| 6299 | 1 | 6959 | 1 |
| 6300 | ], | 6960 | ], |
| 6301 | [ | 6961 | [ |
| 6302 | "{4C5FBA5B-E348-4750-ACE4-E9694D527BFF},1", | 6962 | "{AE89305A-73F4-46B5-B41D-18884685D896},1", |
| 6303 | 1 | 6963 | 1 |
| 6304 | ], | 6964 | ], |
| 6305 | [ | 6965 | [ |
| 6306 | "{BD0EF658-4D0F-42A7-B546-B4F4A77D246B},1", | 6966 | "{551D2637-2AA7-4923-B2DD-31C2E1489FD2},1", |
| 6307 | 1 | 6967 | 1 |
| 6308 | ], | 6968 | ], |
| 6309 | [ | 6969 | [ |
| 6310 | "{6FEA70FA-1451-4A27-8A8E-C4CC3B713CF6},1", | 6970 | "{CBF82C86-3A8B-457F-92A4-1AC7BF436CE9},1", |
| 6311 | 1 | 6971 | 1 |
| 6312 | ], | 6972 | ], |
| 6313 | [ | 6973 | [ |
| 6314 | "{68A1E0E1-7A39-4AB6-AD71-4D8A18878D79},1", | 6974 | "{42010094-E377-4A97-9395-AD45808D0552},1", |
| 6315 | 1 | 6975 | 1 |
| 6316 | ], | 6976 | ], |
| 6317 | [ | 6977 | [ |
| 6318 | "{7E9041BD-301B-4B5B-9456-1C92050E4914},1", | 6978 | "{0F6ABB5E-A021-4E1D-B187-7A72E74000DC},1", |
| 6319 | 1 | 6979 | 1 |
| 6320 | ], | 6980 | ], |
| 6321 | [ | 6981 | [ |
| 6322 | "{EFA77BAE-8A98-4176-B536-B615D1D2ED2B},1", | 6982 | "{F9DA3139-5856-4B11-95C0-356683E3ACFE},1", |
| 6323 | 1 | 6983 | 1 |
| 6324 | ], | 6984 | ], |
| 6325 | [ | 6985 | [ |
| 6326 | "{87AC0239-51A5-47C6-BAF4-926CCF56385C},1", | 6986 | "{439B80E0-9D4C-4A2F-9163-4E301491364D},1", |
| 6327 | 1 | 6987 | 1 |
| 6328 | ], | 6988 | ], |
| 6329 | [ | 6989 | [ |
| 6330 | "{C50BF7FC-F848-4B91-B8C8-743D0AA5F195},1", | 6990 | "{B03B973C-50BC-4B9A-9914-B1D5C0CF6197},1", |
| 6331 | 1 | 6991 | 1 |
| 6332 | ], | 6992 | ], |
| 6333 | [ | 6993 | [ |
| 6334 | "{FD8D07D0-C447-4516-A7DB-F8522B77D4A7},1", | 6994 | "{A459C474-E6EB-4BE5-9193-271799B2860B},1", |
| 6335 | 1 | 6995 | 1 |
| 6336 | ], | 6996 | ], |
| 6337 | [ | 6997 | [ |
| 6338 | "{D2E1FE57-F45D-49C3-B388-95222ACA3886},1", | 6998 | "{4ED14CEE-8560-4125-B488-9945C116741E},1", |
| 6339 | 1 | 6999 | 1 |
| 6340 | ], | 7000 | ], |
| 6341 | [ | 7001 | [ |
| 6342 | "{7F6702B3-C977-436F-82AF-403E2BE950E7},1", | 7002 | "{C4FF8FE4-4C43-461F-98DD-EB1FF2A863B2},1", |
| 6343 | 1 | 7003 | 1 |
| 6344 | ], | 7004 | ], |
| 6345 | [ | 7005 | [ |
| 6346 | "{688C4EA1-80F5-4D85-B9BB-F8633FF3C0CA},1", | 7006 | "{932BE37F-05AA-4EFB-85D8-A33445E738B3},1", |
| 6347 | 1 | 7007 | 1 |
| 6348 | ], | 7008 | ], |
| 6349 | [ | 7009 | [ |
| 6350 | "{754FA957-C432-4502-A5CF-EE44449462C0},1", | 7010 | "{9B755368-1D49-4D7E-8D33-6DC536F57848},1", |
| 6351 | 1 | 7011 | 1 |
| 6352 | ], | 7012 | ], |
| 6353 | [ | 7013 | [ |
| 6354 | "{D2197C55-D962-4DF0-8B14-26F88C06D6FA},1", | 7014 | "{D045EAAF-ACE9-4B4F-979B-E348A4ADE693},1", |
| 6355 | 1 | 7015 | 1 |
| 6356 | ], | 7016 | ], |
| 6357 | [ | 7017 | [ |
| 6358 | "{87B83BA8-A906-4F71-A6CA-3E8133A75D34},1", | 7018 | "{5CA4C59F-B400-4BC6-8B90-30ECAC35CB43},1", |
| 6359 | 1 | 7019 | 1 |
| 6360 | ], | 7020 | ], |
| 6361 | [ | 7021 | [ |
| 6362 | "{F2D6AEBB-98EA-4F10-991F-5243E74AF450},1", | 7022 | "{D63FB383-FB37-44F9-AEBB-E868E903A2BB},1", |
| 6363 | 1 | 7023 | 1 |
| 6364 | ], | 7024 | ], |
| 6365 | [ | 7025 | [ |
| 6366 | "{406687D7-F9E9-491B-85DA-166DE6CD5A60},1", | 7026 | "{C6E54364-6CD0-4764-8180-873030B9EA53},1", |
| 6367 | 1 | 7027 | 1 |
| 6368 | ], | 7028 | ], |
| 6369 | [ | 7029 | [ |
| 6370 | "{1F73BF36-9FC2-495D-844D-B370D5C47DE7},1", | 7030 | "{DEEC391B-F2D1-4EB1-BEB1-57598DFBB982},1", |
| 6371 | 1 | 7031 | 1 |
| 6372 | ], | 7032 | ], |
| 6373 | [ | 7033 | [ |
| 6374 | "{E9BD343F-4A66-4DE2-81F9-3789CCA22818},1", | 7034 | "{9E7616ED-DE19-4625-8EA0-0C5DEB18B690},1", |
| 6375 | 1 | 7035 | 1 |
| 6376 | ], | 7036 | ], |
| 6377 | [ | 7037 | [ |
| 6378 | "{189EA744-BE13-467A-95BA-2667A134C0B0},1", | 7038 | "{89F1748D-6749-4AD0-BAF6-07E9F9C058E5},1", |
| 6379 | 1 | 7039 | 1 |
| 6380 | ], | 7040 | ], |
| 6381 | [ | 7041 | [ |
| 6382 | "{017F5881-F818-4C7C-B9F9-6BCB4085F1D0},1", | 7042 | "{00B10C58-6649-428D-A081-3EBBA6E88E97},1", |
| 6383 | 1 | 7043 | 1 |
| 6384 | ], | 7044 | ], |
| 6385 | [ | 7045 | [ |
| 6386 | "{CD0D17F4-25D8-4076-9AF7-47D9CDEFCF0A},1", | 7046 | "{58E99482-710A-4938-8CFB-88A1C9B946C9},1", |
| 6387 | 1 | 7047 | 1 |
| 6388 | ], | 7048 | ], |
| 6389 | [ | 7049 | [ |
| 6390 | "{CCBB5969-BCE8-498B-BE1A-98FD3A44DB1A},1", | 7050 | "{F721C9D5-9A6A-405F-94C9-9B85A7EEB980},1", |
| 6391 | 1 | 7051 | 1 |
| 6392 | ], | 7052 | ], |
| 6393 | [ | 7053 | [ |
| 6394 | "{2B736B19-2D9C-4E76-A732-478D533D717A},1", | 7054 | "{15A45C01-A9E8-42E2-A267-C7FCE728815E},1", |
| 6395 | 1 | 7055 | 1 |
| 6396 | ] | 7056 | ] |
| 6397 | ] | 7057 | ] |
| 6398 | } | 7058 | } |
| 6399 | }, | 7059 | }, |
| 6400 | "space": "{90FCDB0C-36AE-422F-B4FE-67676826FE36},1" | 7060 | "space": "{A5A2AFBE-1DF1-4190-AAC7-1AD29C9564F2},1" |
| 6401 | }, | 7061 | }, |
| 6402 | { | 7062 | { |
| 6403 | "id": 2, | 7063 | "id": 2, |
| 6404 | "operation": { | 7064 | "operation": { |
| 6405 | "Page": { | 7065 | "Page": { |
| 6406 | "after": { | 7066 | "after": { |
| 6407 | "created": 134335976560000000, | 7067 | "created": 134348779900000000, |
| 6408 | "definitions": { | 7068 | "definitions": { |
| 6409 | "{189EA744-BE13-467A-95BA-2667A134C0B0},18": { | 7069 | "{89F1748D-6749-4AD0-BAF6-07E9F9C058E5},18": { |
| 6410 | "format": { | 7070 | "format": { |
| 6411 | "alignment": null, | 7071 | "alignment": null, |
| 6412 | "bold": null, | 7072 | "bold": null, |
| ... | @@ -6423,6 +7083,7 @@ | ... | @@ -6423,6 +7083,7 @@ |
| 6423 | "line_spacing": null, | 7083 | "line_spacing": null, |
| 6424 | "list_spacing": null, | 7084 | "list_spacing": null, |
| 6425 | "math": null, | 7085 | "math": null, |
| 7086 | "math_object": null, | ||
| 6426 | "rtl": null, | 7087 | "rtl": null, |
| 6427 | "space_after": null, | 7088 | "space_after": null, |
| 6428 | "space_before": null, | 7089 | "space_before": null, |
| ... | @@ -6437,6 +7098,24 @@ | ... | @@ -6437,6 +7098,24 @@ |
| 6437 | } | 7098 | } |
| 6438 | } | 7099 | } |
| 6439 | }, | 7100 | }, |
| 7101 | "identity": [ | ||
| 7102 | 141, | ||
| 7103 | 116, | ||
| 7104 | 241, | ||
| 7105 | 137, | ||
| 7106 | 73, | ||
| 7107 | 103, | ||
| 7108 | 208, | ||
| 7109 | 74, | ||
| 7110 | 186, | ||
| 7111 | 246, | ||
| 7112 | 7, | ||
| 7113 | 233, | ||
| 7114 | 249, | ||
| 7115 | 192, | ||
| 7116 | 88, | ||
| 7117 | 229 | ||
| 7118 | ], | ||
| 6440 | "margin_origin": [ | 7119 | "margin_origin": [ |
| 6441 | 0.0, | 7120 | 0.0, |
| 6442 | 0.0 | 7121 | 0.0 |
| ... | @@ -6444,7 +7123,7 @@ | ... | @@ -6444,7 +7123,7 @@ |
| 6444 | "objects": [ | 7123 | "objects": [ |
| 6445 | { | 7124 | { |
| 6446 | "Outline": { | 7125 | "Outline": { |
| 6447 | "id": "{A05DF797-7FAA-4593-A1F4-7E35812A5257},0", | 7126 | "id": "{91379CE6-91DA-48BA-B306-77E4CEB068B6},32", |
| 6448 | "indents": [], | 7127 | "indents": [], |
| 6449 | "layout": { | 7128 | "layout": { |
| 6450 | "max_height": null, | 7129 | "max_height": null, |
| ... | @@ -6461,7 +7140,7 @@ | ... | @@ -6461,7 +7140,7 @@ |
| 6461 | "content": { | 7140 | "content": { |
| 6462 | "Text": { | 7141 | "Text": { |
| 6463 | "date_field": null, | 7142 | "date_field": null, |
| 6464 | "id": "{D134F5D0-A81E-4663-9AE8-5FF66C8882F1},0", | 7143 | "id": "{91379CE6-91DA-48BA-B306-77E4CEB068B6},31", |
| 6465 | "tags": [], | 7144 | "tags": [], |
| 6466 | "text": { | 7145 | "text": { |
| 6467 | "spans": [ | 7146 | "spans": [ |
| ... | @@ -6483,6 +7162,7 @@ | ... | @@ -6483,6 +7162,7 @@ |
| 6483 | "line_spacing": null, | 7162 | "line_spacing": null, |
| 6484 | "list_spacing": null, | 7163 | "list_spacing": null, |
| 6485 | "math": null, | 7164 | "math": null, |
| 7165 | "math_object": null, | ||
| 6486 | "rtl": null, | 7166 | "rtl": null, |
| 6487 | "space_after": null, | 7167 | "space_after": null, |
| 6488 | "space_before": null, | 7168 | "space_before": null, |
| ... | @@ -6513,6 +7193,7 @@ | ... | @@ -6513,6 +7193,7 @@ |
| 6513 | "line_spacing": null, | 7193 | "line_spacing": null, |
| 6514 | "list_spacing": null, | 7194 | "list_spacing": null, |
| 6515 | "math": null, | 7195 | "math": null, |
| 7196 | "math_object": null, | ||
| 6516 | "rtl": null, | 7197 | "rtl": null, |
| 6517 | "space_after": null, | 7198 | "space_after": null, |
| 6518 | "space_before": null, | 7199 | "space_before": null, |
| ... | @@ -6521,9 +7202,13 @@ | ... | @@ -6521,9 +7202,13 @@ |
| 6521 | "superscript": null, | 7202 | "superscript": null, |
| 6522 | "underline": null | 7203 | "underline": null |
| 6523 | }, | 7204 | }, |
| 6524 | "id": "{D783BABC-D1D5-45A9-86F8-1169830488FC},0", | 7205 | "id": "{91379CE6-91DA-48BA-B306-77E4CEB068B6},33", |
| 6525 | "level": 1, | 7206 | "level": 1, |
| 6526 | "lists": [], | 7207 | "lists": [], |
| 7208 | "media": { | ||
| 7209 | "recordings": [], | ||
| 7210 | "time_ms": null | ||
| 7211 | }, | ||
| 6527 | "parent": null, | 7212 | "parent": null, |
| 6528 | "style": null, | 7213 | "style": null, |
| 6529 | "tags": [] | 7214 | "tags": [] |
| ... | @@ -6536,7 +7221,7 @@ | ... | @@ -6536,7 +7221,7 @@ |
| 6536 | { | 7221 | { |
| 6537 | "Title": { | 7222 | "Title": { |
| 6538 | "date": null, | 7223 | "date": null, |
| 6539 | "id": "{189EA744-BE13-467A-95BA-2667A134C0B0},13", | 7224 | "id": "{89F1748D-6749-4AD0-BAF6-07E9F9C058E5},13", |
| 6540 | "layout": { | 7225 | "layout": { |
| 6541 | "max_height": null, | 7226 | "max_height": null, |
| 6542 | "max_width": null, | 7227 | "max_width": null, |
| ... | @@ -6547,24 +7232,29 @@ | ... | @@ -6547,24 +7232,29 @@ |
| 6547 | }, | 7232 | }, |
| 6548 | "outlines": [ | 7233 | "outlines": [ |
| 6549 | { | 7234 | { |
| 6550 | "id": "{189EA744-BE13-467A-95BA-2667A134C0B0},14", | 7235 | "id": "{89F1748D-6749-4AD0-BAF6-07E9F9C058E5},14", |
| 6551 | "indents": [], | 7236 | "indents": [ |
| 7237 | 18.0, | ||
| 7238 | 0.0, | ||
| 7239 | 27.0, | ||
| 7240 | 27.0 | ||
| 7241 | ], | ||
| 6552 | "layout": { | 7242 | "layout": { |
| 6553 | "max_height": null, | 7243 | "max_height": 21.600000381469727, |
| 6554 | "max_width": null, | 7244 | "max_width": null, |
| 6555 | "reserved_width": null, | 7245 | "reserved_width": null, |
| 6556 | "width_set_by_user": null, | 7246 | "width_set_by_user": null, |
| 6557 | "x": null, | 7247 | "x": null, |
| 6558 | "y": null | 7248 | "y": null |
| 6559 | }, | 7249 | }, |
| 6560 | "min_width": null, | 7250 | "min_width": 162.0, |
| 6561 | "paragraphs": [ | 7251 | "paragraphs": [ |
| 6562 | { | 7252 | { |
| 6563 | "collapsed": false, | 7253 | "collapsed": false, |
| 6564 | "content": { | 7254 | "content": { |
| 6565 | "Text": { | 7255 | "Text": { |
| 6566 | "date_field": null, | 7256 | "date_field": null, |
| 6567 | "id": "{189EA744-BE13-467A-95BA-2667A134C0B0},16", | 7257 | "id": "{89F1748D-6749-4AD0-BAF6-07E9F9C058E5},16", |
| 6568 | "tags": [], | 7258 | "tags": [], |
| 6569 | "text": { | 7259 | "text": { |
| 6570 | "spans": [ | 7260 | "spans": [ |
| ... | @@ -6586,6 +7276,7 @@ | ... | @@ -6586,6 +7276,7 @@ |
| 6586 | "line_spacing": null, | 7276 | "line_spacing": null, |
| 6587 | "list_spacing": null, | 7277 | "list_spacing": null, |
| 6588 | "math": null, | 7278 | "math": null, |
| 7279 | "math_object": null, | ||
| 6589 | "rtl": null, | 7280 | "rtl": null, |
| 6590 | "space_after": null, | 7281 | "space_after": null, |
| 6591 | "space_before": null, | 7282 | "space_before": null, |
| ... | @@ -6616,6 +7307,7 @@ | ... | @@ -6616,6 +7307,7 @@ |
| 6616 | "line_spacing": null, | 7307 | "line_spacing": null, |
| 6617 | "list_spacing": null, | 7308 | "list_spacing": null, |
| 6618 | "math": null, | 7309 | "math": null, |
| 7310 | "math_object": null, | ||
| 6619 | "rtl": null, | 7311 | "rtl": null, |
| 6620 | "space_after": null, | 7312 | "space_after": null, |
| 6621 | "space_before": null, | 7313 | "space_before": null, |
| ... | @@ -6624,15 +7316,19 @@ | ... | @@ -6624,15 +7316,19 @@ |
| 6624 | "superscript": null, | 7316 | "superscript": null, |
| 6625 | "underline": null | 7317 | "underline": null |
| 6626 | }, | 7318 | }, |
| 6627 | "id": "{189EA744-BE13-467A-95BA-2667A134C0B0},15", | 7319 | "id": "{89F1748D-6749-4AD0-BAF6-07E9F9C058E5},15", |
| 6628 | "level": 1, | 7320 | "level": 1, |
| 6629 | "lists": [], | 7321 | "lists": [], |
| 7322 | "media": { | ||
| 7323 | "recordings": [], | ||
| 7324 | "time_ms": null | ||
| 7325 | }, | ||
| 6630 | "parent": null, | 7326 | "parent": null, |
| 6631 | "style": "{189EA744-BE13-467A-95BA-2667A134C0B0},18", | 7327 | "style": "{89F1748D-6749-4AD0-BAF6-07E9F9C058E5},18", |
| 6632 | "tags": [] | 7328 | "tags": [] |
| 6633 | } | 7329 | } |
| 6634 | ], | 7330 | ], |
| 6635 | "title": false, | 7331 | "title": true, |
| 6636 | "unsupported": [] | 7332 | "unsupported": [] |
| 6637 | } | 7333 | } |
| 6638 | ] | 7334 | ] |
| ... | @@ -6643,9 +7339,9 @@ | ... | @@ -6643,9 +7339,9 @@ |
| 6643 | }, | 7339 | }, |
| 6644 | "author": "Author", | 7340 | "author": "Author", |
| 6645 | "before": { | 7341 | "before": { |
| 6646 | "created": 134335976560000000, | 7342 | "created": 134348779900000000, |
| 6647 | "definitions": { | 7343 | "definitions": { |
| 6648 | "{189EA744-BE13-467A-95BA-2667A134C0B0},18": { | 7344 | "{89F1748D-6749-4AD0-BAF6-07E9F9C058E5},18": { |
| 6649 | "format": { | 7345 | "format": { |
| 6650 | "alignment": null, | 7346 | "alignment": null, |
| 6651 | "bold": null, | 7347 | "bold": null, |
| ... | @@ -6662,6 +7358,7 @@ | ... | @@ -6662,6 +7358,7 @@ |
| 6662 | "line_spacing": null, | 7358 | "line_spacing": null, |
| 6663 | "list_spacing": null, | 7359 | "list_spacing": null, |
| 6664 | "math": null, | 7360 | "math": null, |
| 7361 | "math_object": null, | ||
| 6665 | "rtl": null, | 7362 | "rtl": null, |
| 6666 | "space_after": null, | 7363 | "space_after": null, |
| 6667 | "space_before": null, | 7364 | "space_before": null, |
| ... | @@ -6676,6 +7373,24 @@ | ... | @@ -6676,6 +7373,24 @@ |
| 6676 | } | 7373 | } |
| 6677 | } | 7374 | } |
| 6678 | }, | 7375 | }, |
| 7376 | "identity": [ | ||
| 7377 | 141, | ||
| 7378 | 116, | ||
| 7379 | 241, | ||
| 7380 | 137, | ||
| 7381 | 73, | ||
| 7382 | 103, | ||
| 7383 | 208, | ||
| 7384 | 74, | ||
| 7385 | 186, | ||
| 7386 | 246, | ||
| 7387 | 7, | ||
| 7388 | 233, | ||
| 7389 | 249, | ||
| 7390 | 192, | ||
| 7391 | 88, | ||
| 7392 | 229 | ||
| 7393 | ], | ||
| 6679 | "margin_origin": [ | 7394 | "margin_origin": [ |
| 6680 | 0.0, | 7395 | 0.0, |
| 6681 | 0.0 | 7396 | 0.0 |
| ... | @@ -6684,7 +7399,7 @@ | ... | @@ -6684,7 +7399,7 @@ |
| 6684 | { | 7399 | { |
| 6685 | "Title": { | 7400 | "Title": { |
| 6686 | "date": null, | 7401 | "date": null, |
| 6687 | "id": "{189EA744-BE13-467A-95BA-2667A134C0B0},13", | 7402 | "id": "{89F1748D-6749-4AD0-BAF6-07E9F9C058E5},13", |
| 6688 | "layout": { | 7403 | "layout": { |
| 6689 | "max_height": null, | 7404 | "max_height": null, |
| 6690 | "max_width": null, | 7405 | "max_width": null, |
| ... | @@ -6695,24 +7410,29 @@ | ... | @@ -6695,24 +7410,29 @@ |
| 6695 | }, | 7410 | }, |
| 6696 | "outlines": [ | 7411 | "outlines": [ |
| 6697 | { | 7412 | { |
| 6698 | "id": "{189EA744-BE13-467A-95BA-2667A134C0B0},14", | 7413 | "id": "{89F1748D-6749-4AD0-BAF6-07E9F9C058E5},14", |
| 6699 | "indents": [], | 7414 | "indents": [ |
| 7415 | 18.0, | ||
| 7416 | 0.0, | ||
| 7417 | 27.0, | ||
| 7418 | 27.0 | ||
| 7419 | ], | ||
| 6700 | "layout": { | 7420 | "layout": { |
| 6701 | "max_height": null, | 7421 | "max_height": 21.600000381469727, |
| 6702 | "max_width": null, | 7422 | "max_width": null, |
| 6703 | "reserved_width": null, | 7423 | "reserved_width": null, |
| 6704 | "width_set_by_user": null, | 7424 | "width_set_by_user": null, |
| 6705 | "x": null, | 7425 | "x": null, |
| 6706 | "y": null | 7426 | "y": null |
| 6707 | }, | 7427 | }, |
| 6708 | "min_width": null, | 7428 | "min_width": 162.0, |
| 6709 | "paragraphs": [ | 7429 | "paragraphs": [ |
| 6710 | { | 7430 | { |
| 6711 | "collapsed": false, | 7431 | "collapsed": false, |
| 6712 | "content": { | 7432 | "content": { |
| 6713 | "Text": { | 7433 | "Text": { |
| 6714 | "date_field": null, | 7434 | "date_field": null, |
| 6715 | "id": "{189EA744-BE13-467A-95BA-2667A134C0B0},16", | 7435 | "id": "{89F1748D-6749-4AD0-BAF6-07E9F9C058E5},16", |
| 6716 | "tags": [], | 7436 | "tags": [], |
| 6717 | "text": { | 7437 | "text": { |
| 6718 | "spans": [ | 7438 | "spans": [ |
| ... | @@ -6734,6 +7454,7 @@ | ... | @@ -6734,6 +7454,7 @@ |
| 6734 | "line_spacing": null, | 7454 | "line_spacing": null, |
| 6735 | "list_spacing": null, | 7455 | "list_spacing": null, |
| 6736 | "math": null, | 7456 | "math": null, |
| 7457 | "math_object": null, | ||
| 6737 | "rtl": null, | 7458 | "rtl": null, |
| 6738 | "space_after": null, | 7459 | "space_after": null, |
| 6739 | "space_before": null, | 7460 | "space_before": null, |
| ... | @@ -6764,6 +7485,7 @@ | ... | @@ -6764,6 +7485,7 @@ |
| 6764 | "line_spacing": null, | 7485 | "line_spacing": null, |
| 6765 | "list_spacing": null, | 7486 | "list_spacing": null, |
| 6766 | "math": null, | 7487 | "math": null, |
| 7488 | "math_object": null, | ||
| 6767 | "rtl": null, | 7489 | "rtl": null, |
| 6768 | "space_after": null, | 7490 | "space_after": null, |
| 6769 | "space_before": null, | 7491 | "space_before": null, |
| ... | @@ -6772,15 +7494,19 @@ | ... | @@ -6772,15 +7494,19 @@ |
| 6772 | "superscript": null, | 7494 | "superscript": null, |
| 6773 | "underline": null | 7495 | "underline": null |
| 6774 | }, | 7496 | }, |
| 6775 | "id": "{189EA744-BE13-467A-95BA-2667A134C0B0},15", | 7497 | "id": "{89F1748D-6749-4AD0-BAF6-07E9F9C058E5},15", |
| 6776 | "level": 1, | 7498 | "level": 1, |
| 6777 | "lists": [], | 7499 | "lists": [], |
| 7500 | "media": { | ||
| 7501 | "recordings": [], | ||
| 7502 | "time_ms": null | ||
| 7503 | }, | ||
| 6778 | "parent": null, | 7504 | "parent": null, |
| 6779 | "style": "{189EA744-BE13-467A-95BA-2667A134C0B0},18", | 7505 | "style": "{89F1748D-6749-4AD0-BAF6-07E9F9C058E5},18", |
| 6780 | "tags": [] | 7506 | "tags": [] |
| 6781 | } | 7507 | } |
| 6782 | ], | 7508 | ], |
| 6783 | "title": false, | 7509 | "title": true, |
| 6784 | "unsupported": [] | 7510 | "unsupported": [] |
| 6785 | } | 7511 | } |
| 6786 | ] | 7512 | ] |
| ... | @@ -6791,7 +7517,7 @@ | ... | @@ -6791,7 +7517,7 @@ |
| 6791 | } | 7517 | } |
| 6792 | } | 7518 | } |
| 6793 | }, | 7519 | }, |
| 6794 | "space": "{189EA744-BE13-467A-95BA-2667A134C0B0},1" | 7520 | "space": "{89F1748D-6749-4AD0-BAF6-07E9F9C058E5},1" |
| 6795 | } | 7521 | } |
| 6796 | ], | 7522 | ], |
| 6797 | [ | 7523 | [ |
| ... | @@ -6802,215 +7528,215 @@ | ... | @@ -6802,215 +7528,215 @@ |
| 6802 | "edits": [ | 7528 | "edits": [ |
| 6803 | { | 7529 | { |
| 6804 | "guid": [ | 7530 | "guid": [ |
| 6805 | 137, | 7531 | 69, |
| 6806 | 150, | 7532 | 87, |
| 6807 | 22, | 7533 | 190, |
| 6808 | 112, | 7534 | 154, |
| 6809 | 113, | 7535 | 161, |
| 6810 | 130, | 7536 | 45, |
| 6811 | 213, | 7537 | 245, |
| 6812 | 65, | 7538 | 76, |
| 6813 | 143, | 7539 | 131, |
| 6814 | 6, | 7540 | 158, |
| 6815 | 73, | 7541 | 203, |
| 6816 | 88, | 7542 | 234, |
| 6817 | 176, | 7543 | 216, |
| 6818 | 192, | 7544 | 244, |
| 6819 | 23, | 7545 | 254, |
| 6820 | 119 | 7546 | 235 |
| 6821 | ], | 7547 | ], |
| 6822 | "level": 1, | 7548 | "level": 1, |
| 6823 | "position": { | 7549 | "position": { |
| 6824 | "Before": "{CD0D17F4-25D8-4076-9AF7-47D9CDEFCF0A},1" | 7550 | "Before": "{58E99482-710A-4938-8CFB-88A1C9B946C9},1" |
| 6825 | }, | 7551 | }, |
| 6826 | "space": "{CCBB5969-BCE8-498B-BE1A-98FD3A44DB1A},1" | 7552 | "space": "{F721C9D5-9A6A-405F-94C9-9B85A7EEB980},1" |
| 6827 | }, | 7553 | }, |
| 6828 | { | 7554 | { |
| 6829 | "guid": [ | 7555 | "guid": [ |
| 6830 | 65, | 7556 | 35, |
| 7557 | 252, | ||
| 7558 | 68, | ||
| 7559 | 95, | ||
| 7560 | 80, | ||
| 7561 | 200, | ||
| 7562 | 215, | ||
| 6831 | 77, | 7563 | 77, |
| 6832 | 116, | 7564 | 190, |
| 6833 | 151, | 7565 | 11, |
| 6834 | 20, | 7566 | 114, |
| 6835 | 16, | 7567 | 173, |
| 6836 | 115, | 7568 | 3, |
| 6837 | 66, | 7569 | 70, |
| 6838 | 138, | 7570 | 77, |
| 6839 | 169, | 7571 | 30 |
| 6840 | 25, | ||
| 6841 | 24, | ||
| 6842 | 111, | ||
| 6843 | 125, | ||
| 6844 | 154, | ||
| 6845 | 123 | ||
| 6846 | ], | 7572 | ], |
| 6847 | "level": 2, | 7573 | "level": 2, |
| 6848 | "position": "Keep", | 7574 | "position": "Keep", |
| 6849 | "space": "{2B736B19-2D9C-4E76-A732-478D533D717A},1" | 7575 | "space": "{15A45C01-A9E8-42E2-A267-C7FCE728815E},1" |
| 6850 | } | 7576 | } |
| 6851 | ], | 7577 | ], |
| 6852 | "observed": [ | 7578 | "observed": [ |
| 6853 | [ | 7579 | [ |
| 6854 | "{869D5929-6E5E-4EDB-9639-4C56977DD447},1", | 7580 | "{0B37E922-551A-48F8-BCAC-4AB3503B4D28},1", |
| 6855 | 1 | 7581 | 1 |
| 6856 | ], | 7582 | ], |
| 6857 | [ | 7583 | [ |
| 6858 | "{B0B53F94-0105-4C59-B65B-9473F49BFF41},1", | 7584 | "{74492C4A-164B-40E9-B297-4D3B4685347D},1", |
| 6859 | 1 | 7585 | 1 |
| 6860 | ], | 7586 | ], |
| 6861 | [ | 7587 | [ |
| 6862 | "{0E98EE82-B78F-4872-8BD9-5D9B23A817C5},1", | 7588 | "{DAFF533D-608B-4735-BA1D-2354E8CB8A69},1", |
| 6863 | 1 | 7589 | 1 |
| 6864 | ], | 7590 | ], |
| 6865 | [ | 7591 | [ |
| 6866 | "{C837A033-F055-4455-B45F-17D82016921C},1", | 7592 | "{8B766F11-2A4F-4D95-8473-D3CA68129E11},1", |
| 6867 | 1 | 7593 | 1 |
| 6868 | ], | 7594 | ], |
| 6869 | [ | 7595 | [ |
| 6870 | "{89D86121-BF5C-418B-BB92-CBFFA8EF545F},1", | 7596 | "{28E0CC5F-68BC-4A86-9ED7-114FFD3BAE9A},1", |
| 6871 | 1 | 7597 | 1 |
| 6872 | ], | 7598 | ], |
| 6873 | [ | 7599 | [ |
| 6874 | "{57E835EA-2799-4118-B55C-7BB1E51C6941},1", | 7600 | "{31FFA5F9-C1B8-412D-A2D6-7ACC3CC16FD0},1", |
| 6875 | 1 | 7601 | 1 |
| 6876 | ], | 7602 | ], |
| 6877 | [ | 7603 | [ |
| 6878 | "{F7EECCCF-B6B4-44F2-A543-36837EC18B8A},1", | 7604 | "{AFCD09F6-1085-4582-BDB2-4B91E15D2088},1", |
| 6879 | 1 | 7605 | 1 |
| 6880 | ], | 7606 | ], |
| 6881 | [ | 7607 | [ |
| 6882 | "{361A0F09-EA63-4F2D-9620-C061D4E4707E},1", | 7608 | "{A94D5882-C902-4CAE-B38E-2ED56EEF177F},1", |
| 6883 | 1 | 7609 | 1 |
| 6884 | ], | 7610 | ], |
| 6885 | [ | 7611 | [ |
| 6886 | "{EB3940F1-F255-42BE-8C31-DD09E86DBAAF},1", | 7612 | "{5AB37A0F-5702-4C8E-A8C6-79ED6ED1E69F},1", |
| 6887 | 1 | 7613 | 1 |
| 6888 | ], | 7614 | ], |
| 6889 | [ | 7615 | [ |
| 6890 | "{C2870D07-8ADC-44BB-9F12-15D6C3708689},1", | 7616 | "{F212795E-0E4B-4844-A8A6-ABD7F7025124},1", |
| 6891 | 1 | 7617 | 1 |
| 6892 | ], | 7618 | ], |
| 6893 | [ | 7619 | [ |
| 6894 | "{1E0C6331-32C9-4EF9-8087-150A8669FA91},1", | 7620 | "{1786F2CA-0A43-4081-8B55-B207EEE0331C},1", |
| 6895 | 1 | 7621 | 1 |
| 6896 | ], | 7622 | ], |
| 6897 | [ | 7623 | [ |
| 6898 | "{ED9769DD-99D6-4BE2-B980-580DA9C4DFCE},1", | 7624 | "{3E966EF8-B7B4-483F-B241-8F21DF318AB3},1", |
| 6899 | 1 | 7625 | 1 |
| 6900 | ], | 7626 | ], |
| 6901 | [ | 7627 | [ |
| 6902 | "{17C30054-7129-4A10-9D00-4D5545E4F4BA},1", | 7628 | "{0D361C76-92FD-4B76-901B-809C2C3C3CE6},1", |
| 6903 | 1 | 7629 | 1 |
| 6904 | ], | 7630 | ], |
| 6905 | [ | 7631 | [ |
| 6906 | "{4C5FBA5B-E348-4750-ACE4-E9694D527BFF},1", | 7632 | "{AE89305A-73F4-46B5-B41D-18884685D896},1", |
| 6907 | 1 | 7633 | 1 |
| 6908 | ], | 7634 | ], |
| 6909 | [ | 7635 | [ |
| 6910 | "{BD0EF658-4D0F-42A7-B546-B4F4A77D246B},1", | 7636 | "{551D2637-2AA7-4923-B2DD-31C2E1489FD2},1", |
| 6911 | 1 | 7637 | 1 |
| 6912 | ], | 7638 | ], |
| 6913 | [ | 7639 | [ |
| 6914 | "{6FEA70FA-1451-4A27-8A8E-C4CC3B713CF6},1", | 7640 | "{CBF82C86-3A8B-457F-92A4-1AC7BF436CE9},1", |
| 6915 | 1 | 7641 | 1 |
| 6916 | ], | 7642 | ], |
| 6917 | [ | 7643 | [ |
| 6918 | "{68A1E0E1-7A39-4AB6-AD71-4D8A18878D79},1", | 7644 | "{42010094-E377-4A97-9395-AD45808D0552},1", |
| 6919 | 1 | 7645 | 1 |
| 6920 | ], | 7646 | ], |
| 6921 | [ | 7647 | [ |
| 6922 | "{7E9041BD-301B-4B5B-9456-1C92050E4914},1", | 7648 | "{0F6ABB5E-A021-4E1D-B187-7A72E74000DC},1", |
| 6923 | 1 | 7649 | 1 |
| 6924 | ], | 7650 | ], |
| 6925 | [ | 7651 | [ |
| 6926 | "{EFA77BAE-8A98-4176-B536-B615D1D2ED2B},1", | 7652 | "{F9DA3139-5856-4B11-95C0-356683E3ACFE},1", |
| 6927 | 1 | 7653 | 1 |
| 6928 | ], | 7654 | ], |
| 6929 | [ | 7655 | [ |
| 6930 | "{87AC0239-51A5-47C6-BAF4-926CCF56385C},1", | 7656 | "{439B80E0-9D4C-4A2F-9163-4E301491364D},1", |
| 6931 | 1 | 7657 | 1 |
| 6932 | ], | 7658 | ], |
| 6933 | [ | 7659 | [ |
| 6934 | "{C50BF7FC-F848-4B91-B8C8-743D0AA5F195},1", | 7660 | "{B03B973C-50BC-4B9A-9914-B1D5C0CF6197},1", |
| 6935 | 1 | 7661 | 1 |
| 6936 | ], | 7662 | ], |
| 6937 | [ | 7663 | [ |
| 6938 | "{FD8D07D0-C447-4516-A7DB-F8522B77D4A7},1", | 7664 | "{A459C474-E6EB-4BE5-9193-271799B2860B},1", |
| 6939 | 1 | 7665 | 1 |
| 6940 | ], | 7666 | ], |
| 6941 | [ | 7667 | [ |
| 6942 | "{D2E1FE57-F45D-49C3-B388-95222ACA3886},1", | 7668 | "{4ED14CEE-8560-4125-B488-9945C116741E},1", |
| 6943 | 1 | 7669 | 1 |
| 6944 | ], | 7670 | ], |
| 6945 | [ | 7671 | [ |
| 6946 | "{7F6702B3-C977-436F-82AF-403E2BE950E7},1", | 7672 | "{C4FF8FE4-4C43-461F-98DD-EB1FF2A863B2},1", |
| 6947 | 1 | 7673 | 1 |
| 6948 | ], | 7674 | ], |
| 6949 | [ | 7675 | [ |
| 6950 | "{688C4EA1-80F5-4D85-B9BB-F8633FF3C0CA},1", | 7676 | "{932BE37F-05AA-4EFB-85D8-A33445E738B3},1", |
| 6951 | 1 | 7677 | 1 |
| 6952 | ], | 7678 | ], |
| 6953 | [ | 7679 | [ |
| 6954 | "{754FA957-C432-4502-A5CF-EE44449462C0},1", | 7680 | "{9B755368-1D49-4D7E-8D33-6DC536F57848},1", |
| 6955 | 1 | 7681 | 1 |
| 6956 | ], | 7682 | ], |
| 6957 | [ | 7683 | [ |
| 6958 | "{D2197C55-D962-4DF0-8B14-26F88C06D6FA},1", | 7684 | "{D045EAAF-ACE9-4B4F-979B-E348A4ADE693},1", |
| 6959 | 1 | 7685 | 1 |
| 6960 | ], | 7686 | ], |
| 6961 | [ | 7687 | [ |
| 6962 | "{87B83BA8-A906-4F71-A6CA-3E8133A75D34},1", | 7688 | "{5CA4C59F-B400-4BC6-8B90-30ECAC35CB43},1", |
| 6963 | 1 | 7689 | 1 |
| 6964 | ], | 7690 | ], |
| 6965 | [ | 7691 | [ |
| 6966 | "{F2D6AEBB-98EA-4F10-991F-5243E74AF450},1", | 7692 | "{D63FB383-FB37-44F9-AEBB-E868E903A2BB},1", |
| 6967 | 1 | 7693 | 1 |
| 6968 | ], | 7694 | ], |
| 6969 | [ | 7695 | [ |
| 6970 | "{406687D7-F9E9-491B-85DA-166DE6CD5A60},1", | 7696 | "{C6E54364-6CD0-4764-8180-873030B9EA53},1", |
| 6971 | 1 | 7697 | 1 |
| 6972 | ], | 7698 | ], |
| 6973 | [ | 7699 | [ |
| 6974 | "{1F73BF36-9FC2-495D-844D-B370D5C47DE7},1", | 7700 | "{DEEC391B-F2D1-4EB1-BEB1-57598DFBB982},1", |
| 6975 | 1 | 7701 | 1 |
| 6976 | ], | 7702 | ], |
| 6977 | [ | 7703 | [ |
| 6978 | "{E9BD343F-4A66-4DE2-81F9-3789CCA22818},1", | 7704 | "{9E7616ED-DE19-4625-8EA0-0C5DEB18B690},1", |
| 6979 | 1 | 7705 | 1 |
| 6980 | ], | 7706 | ], |
| 6981 | [ | 7707 | [ |
| 6982 | "{189EA744-BE13-467A-95BA-2667A134C0B0},1", | 7708 | "{89F1748D-6749-4AD0-BAF6-07E9F9C058E5},1", |
| 6983 | 1 | 7709 | 1 |
| 6984 | ], | 7710 | ], |
| 6985 | [ | 7711 | [ |
| 6986 | "{017F5881-F818-4C7C-B9F9-6BCB4085F1D0},1", | 7712 | "{00B10C58-6649-428D-A081-3EBBA6E88E97},1", |
| 6987 | 1 | 7713 | 1 |
| 6988 | ], | 7714 | ], |
| 6989 | [ | 7715 | [ |
| 6990 | "{CD0D17F4-25D8-4076-9AF7-47D9CDEFCF0A},1", | 7716 | "{58E99482-710A-4938-8CFB-88A1C9B946C9},1", |
| 6991 | 1 | 7717 | 1 |
| 6992 | ], | 7718 | ], |
| 6993 | [ | 7719 | [ |
| 6994 | "{CCBB5969-BCE8-498B-BE1A-98FD3A44DB1A},1", | 7720 | "{F721C9D5-9A6A-405F-94C9-9B85A7EEB980},1", |
| 6995 | 1 | 7721 | 1 |
| 6996 | ], | 7722 | ], |
| 6997 | [ | 7723 | [ |
| 6998 | "{2B736B19-2D9C-4E76-A732-478D533D717A},1", | 7724 | "{15A45C01-A9E8-42E2-A267-C7FCE728815E},1", |
| 6999 | 1 | 7725 | 1 |
| 7000 | ] | 7726 | ] |
| 7001 | ] | 7727 | ] |
| 7002 | } | 7728 | } |
| 7003 | }, | 7729 | }, |
| 7004 | "space": "{90FCDB0C-36AE-422F-B4FE-67676826FE36},1" | 7730 | "space": "{A5A2AFBE-1DF1-4190-AAC7-1AD29C9564F2},1" |
| 7005 | }, | 7731 | }, |
| 7006 | { | 7732 | { |
| 7007 | "id": 2, | 7733 | "id": 2, |
| 7008 | "operation": { | 7734 | "operation": { |
| 7009 | "Page": { | 7735 | "Page": { |
| 7010 | "after": { | 7736 | "after": { |
| 7011 | "created": 134335976560000000, | 7737 | "created": 134348779900000000, |
| 7012 | "definitions": { | 7738 | "definitions": { |
| 7013 | "{CCBB5969-BCE8-498B-BE1A-98FD3A44DB1A},18": { | 7739 | "{F721C9D5-9A6A-405F-94C9-9B85A7EEB980},18": { |
| 7014 | "format": { | 7740 | "format": { |
| 7015 | "alignment": null, | 7741 | "alignment": null, |
| 7016 | "bold": null, | 7742 | "bold": null, |
| ... | @@ -7027,6 +7753,7 @@ | ... | @@ -7027,6 +7753,7 @@ |
| 7027 | "line_spacing": null, | 7753 | "line_spacing": null, |
| 7028 | "list_spacing": null, | 7754 | "list_spacing": null, |
| 7029 | "math": null, | 7755 | "math": null, |
| 7756 | "math_object": null, | ||
| 7030 | "rtl": null, | 7757 | "rtl": null, |
| 7031 | "space_after": null, | 7758 | "space_after": null, |
| 7032 | "space_before": null, | 7759 | "space_before": null, |
| ... | @@ -7041,6 +7768,24 @@ | ... | @@ -7041,6 +7768,24 @@ |
| 7041 | } | 7768 | } |
| 7042 | } | 7769 | } |
| 7043 | }, | 7770 | }, |
| 7771 | "identity": [ | ||
| 7772 | 213, | ||
| 7773 | 201, | ||
| 7774 | 33, | ||
| 7775 | 247, | ||
| 7776 | 106, | ||
| 7777 | 154, | ||
| 7778 | 95, | ||
| 7779 | 64, | ||
| 7780 | 148, | ||
| 7781 | 201, | ||
| 7782 | 155, | ||
| 7783 | 133, | ||
| 7784 | 167, | ||
| 7785 | 238, | ||
| 7786 | 185, | ||
| 7787 | 128 | ||
| 7788 | ], | ||
| 7044 | "margin_origin": [ | 7789 | "margin_origin": [ |
| 7045 | 0.0, | 7790 | 0.0, |
| 7046 | 0.0 | 7791 | 0.0 |
| ... | @@ -7048,7 +7793,7 @@ | ... | @@ -7048,7 +7793,7 @@ |
| 7048 | "objects": [ | 7793 | "objects": [ |
| 7049 | { | 7794 | { |
| 7050 | "Outline": { | 7795 | "Outline": { |
| 7051 | "id": "{00E55A56-1362-4A11-ACD3-2117BCCE619E},0", | 7796 | "id": "{91379CE6-91DA-48BA-B306-77E4CEB068B6},35", |
| 7052 | "indents": [], | 7797 | "indents": [], |
| 7053 | "layout": { | 7798 | "layout": { |
| 7054 | "max_height": null, | 7799 | "max_height": null, |
| ... | @@ -7065,7 +7810,7 @@ | ... | @@ -7065,7 +7810,7 @@ |
| 7065 | "content": { | 7810 | "content": { |
| 7066 | "Text": { | 7811 | "Text": { |
| 7067 | "date_field": null, | 7812 | "date_field": null, |
| 7068 | "id": "{AD3FBA83-B057-48DF-9731-DEBD6F95BDBE},0", | 7813 | "id": "{91379CE6-91DA-48BA-B306-77E4CEB068B6},34", |
| 7069 | "tags": [], | 7814 | "tags": [], |
| 7070 | "text": { | 7815 | "text": { |
| 7071 | "spans": [ | 7816 | "spans": [ |
| ... | @@ -7087,6 +7832,7 @@ | ... | @@ -7087,6 +7832,7 @@ |
| 7087 | "line_spacing": null, | 7832 | "line_spacing": null, |
| 7088 | "list_spacing": null, | 7833 | "list_spacing": null, |
| 7089 | "math": null, | 7834 | "math": null, |
| 7835 | "math_object": null, | ||
| 7090 | "rtl": null, | 7836 | "rtl": null, |
| 7091 | "space_after": null, | 7837 | "space_after": null, |
| 7092 | "space_before": null, | 7838 | "space_before": null, |
| ... | @@ -7117,6 +7863,7 @@ | ... | @@ -7117,6 +7863,7 @@ |
| 7117 | "line_spacing": null, | 7863 | "line_spacing": null, |
| 7118 | "list_spacing": null, | 7864 | "list_spacing": null, |
| 7119 | "math": null, | 7865 | "math": null, |
| 7866 | "math_object": null, | ||
| 7120 | "rtl": null, | 7867 | "rtl": null, |
| 7121 | "space_after": null, | 7868 | "space_after": null, |
| 7122 | "space_before": null, | 7869 | "space_before": null, |
| ... | @@ -7125,9 +7872,13 @@ | ... | @@ -7125,9 +7872,13 @@ |
| 7125 | "superscript": null, | 7872 | "superscript": null, |
| 7126 | "underline": null | 7873 | "underline": null |
| 7127 | }, | 7874 | }, |
| 7128 | "id": "{77E85A17-9EF5-4C79-B11B-48A5CE7FE0FD},0", | 7875 | "id": "{91379CE6-91DA-48BA-B306-77E4CEB068B6},36", |
| 7129 | "level": 1, | 7876 | "level": 1, |
| 7130 | "lists": [], | 7877 | "lists": [], |
| 7878 | "media": { | ||
| 7879 | "recordings": [], | ||
| 7880 | "time_ms": null | ||
| 7881 | }, | ||
| 7131 | "parent": null, | 7882 | "parent": null, |
| 7132 | "style": null, | 7883 | "style": null, |
| 7133 | "tags": [] | 7884 | "tags": [] |
| ... | @@ -7140,7 +7891,7 @@ | ... | @@ -7140,7 +7891,7 @@ |
| 7140 | { | 7891 | { |
| 7141 | "Title": { | 7892 | "Title": { |
| 7142 | "date": null, | 7893 | "date": null, |
| 7143 | "id": "{CCBB5969-BCE8-498B-BE1A-98FD3A44DB1A},13", | 7894 | "id": "{F721C9D5-9A6A-405F-94C9-9B85A7EEB980},13", |
| 7144 | "layout": { | 7895 | "layout": { |
| 7145 | "max_height": null, | 7896 | "max_height": null, |
| 7146 | "max_width": null, | 7897 | "max_width": null, |
| ... | @@ -7151,24 +7902,29 @@ | ... | @@ -7151,24 +7902,29 @@ |
| 7151 | }, | 7902 | }, |
| 7152 | "outlines": [ | 7903 | "outlines": [ |
| 7153 | { | 7904 | { |
| 7154 | "id": "{CCBB5969-BCE8-498B-BE1A-98FD3A44DB1A},14", | 7905 | "id": "{F721C9D5-9A6A-405F-94C9-9B85A7EEB980},14", |
| 7155 | "indents": [], | 7906 | "indents": [ |
| 7907 | 18.0, | ||
| 7908 | 0.0, | ||
| 7909 | 27.0, | ||
| 7910 | 27.0 | ||
| 7911 | ], | ||
| 7156 | "layout": { | 7912 | "layout": { |
| 7157 | "max_height": null, | 7913 | "max_height": 21.600000381469727, |
| 7158 | "max_width": null, | 7914 | "max_width": null, |
| 7159 | "reserved_width": null, | 7915 | "reserved_width": null, |
| 7160 | "width_set_by_user": null, | 7916 | "width_set_by_user": null, |
| 7161 | "x": null, | 7917 | "x": null, |
| 7162 | "y": null | 7918 | "y": null |
| 7163 | }, | 7919 | }, |
| 7164 | "min_width": null, | 7920 | "min_width": 162.0, |
| 7165 | "paragraphs": [ | 7921 | "paragraphs": [ |
| 7166 | { | 7922 | { |
| 7167 | "collapsed": false, | 7923 | "collapsed": false, |
| 7168 | "content": { | 7924 | "content": { |
| 7169 | "Text": { | 7925 | "Text": { |
| 7170 | "date_field": null, | 7926 | "date_field": null, |
| 7171 | "id": "{CCBB5969-BCE8-498B-BE1A-98FD3A44DB1A},16", | 7927 | "id": "{F721C9D5-9A6A-405F-94C9-9B85A7EEB980},16", |
| 7172 | "tags": [], | 7928 | "tags": [], |
| 7173 | "text": { | 7929 | "text": { |
| 7174 | "spans": [ | 7930 | "spans": [ |
| ... | @@ -7190,6 +7946,7 @@ | ... | @@ -7190,6 +7946,7 @@ |
| 7190 | "line_spacing": null, | 7946 | "line_spacing": null, |
| 7191 | "list_spacing": null, | 7947 | "list_spacing": null, |
| 7192 | "math": null, | 7948 | "math": null, |
| 7949 | "math_object": null, | ||
| 7193 | "rtl": null, | 7950 | "rtl": null, |
| 7194 | "space_after": null, | 7951 | "space_after": null, |
| 7195 | "space_before": null, | 7952 | "space_before": null, |
| ... | @@ -7220,6 +7977,7 @@ | ... | @@ -7220,6 +7977,7 @@ |
| 7220 | "line_spacing": null, | 7977 | "line_spacing": null, |
| 7221 | "list_spacing": null, | 7978 | "list_spacing": null, |
| 7222 | "math": null, | 7979 | "math": null, |
| 7980 | "math_object": null, | ||
| 7223 | "rtl": null, | 7981 | "rtl": null, |
| 7224 | "space_after": null, | 7982 | "space_after": null, |
| 7225 | "space_before": null, | 7983 | "space_before": null, |
| ... | @@ -7228,15 +7986,19 @@ | ... | @@ -7228,15 +7986,19 @@ |
| 7228 | "superscript": null, | 7986 | "superscript": null, |
| 7229 | "underline": null | 7987 | "underline": null |
| 7230 | }, | 7988 | }, |
| 7231 | "id": "{CCBB5969-BCE8-498B-BE1A-98FD3A44DB1A},15", | 7989 | "id": "{F721C9D5-9A6A-405F-94C9-9B85A7EEB980},15", |
| 7232 | "level": 1, | 7990 | "level": 1, |
| 7233 | "lists": [], | 7991 | "lists": [], |
| 7992 | "media": { | ||
| 7993 | "recordings": [], | ||
| 7994 | "time_ms": null | ||
| 7995 | }, | ||
| 7234 | "parent": null, | 7996 | "parent": null, |
| 7235 | "style": "{CCBB5969-BCE8-498B-BE1A-98FD3A44DB1A},18", | 7997 | "style": "{F721C9D5-9A6A-405F-94C9-9B85A7EEB980},18", |
| 7236 | "tags": [] | 7998 | "tags": [] |
| 7237 | } | 7999 | } |
| 7238 | ], | 8000 | ], |
| 7239 | "title": false, | 8001 | "title": true, |
| 7240 | "unsupported": [] | 8002 | "unsupported": [] |
| 7241 | } | 8003 | } |
| 7242 | ] | 8004 | ] |
| ... | @@ -7247,9 +8009,9 @@ | ... | @@ -7247,9 +8009,9 @@ |
| 7247 | }, | 8009 | }, |
| 7248 | "author": "Author", | 8010 | "author": "Author", |
| 7249 | "before": { | 8011 | "before": { |
| 7250 | "created": 134335976560000000, | 8012 | "created": 134348779900000000, |
| 7251 | "definitions": { | 8013 | "definitions": { |
| 7252 | "{CCBB5969-BCE8-498B-BE1A-98FD3A44DB1A},18": { | 8014 | "{F721C9D5-9A6A-405F-94C9-9B85A7EEB980},18": { |
| 7253 | "format": { | 8015 | "format": { |
| 7254 | "alignment": null, | 8016 | "alignment": null, |
| 7255 | "bold": null, | 8017 | "bold": null, |
| ... | @@ -7266,6 +8028,7 @@ | ... | @@ -7266,6 +8028,7 @@ |
| 7266 | "line_spacing": null, | 8028 | "line_spacing": null, |
| 7267 | "list_spacing": null, | 8029 | "list_spacing": null, |
| 7268 | "math": null, | 8030 | "math": null, |
| 8031 | "math_object": null, | ||
| 7269 | "rtl": null, | 8032 | "rtl": null, |
| 7270 | "space_after": null, | 8033 | "space_after": null, |
| 7271 | "space_before": null, | 8034 | "space_before": null, |
| ... | @@ -7280,6 +8043,24 @@ | ... | @@ -7280,6 +8043,24 @@ |
| 7280 | } | 8043 | } |
| 7281 | } | 8044 | } |
| 7282 | }, | 8045 | }, |
| 8046 | "identity": [ | ||
| 8047 | 213, | ||
| 8048 | 201, | ||
| 8049 | 33, | ||
| 8050 | 247, | ||
| 8051 | 106, | ||
| 8052 | 154, | ||
| 8053 | 95, | ||
| 8054 | 64, | ||
| 8055 | 148, | ||
| 8056 | 201, | ||
| 8057 | 155, | ||
| 8058 | 133, | ||
| 8059 | 167, | ||
| 8060 | 238, | ||
| 8061 | 185, | ||
| 8062 | 128 | ||
| 8063 | ], | ||
| 7283 | "margin_origin": [ | 8064 | "margin_origin": [ |
| 7284 | 0.0, | 8065 | 0.0, |
| 7285 | 0.0 | 8066 | 0.0 |
| ... | @@ -7288,7 +8069,7 @@ | ... | @@ -7288,7 +8069,7 @@ |
| 7288 | { | 8069 | { |
| 7289 | "Title": { | 8070 | "Title": { |
| 7290 | "date": null, | 8071 | "date": null, |
| 7291 | "id": "{CCBB5969-BCE8-498B-BE1A-98FD3A44DB1A},13", | 8072 | "id": "{F721C9D5-9A6A-405F-94C9-9B85A7EEB980},13", |
| 7292 | "layout": { | 8073 | "layout": { |
| 7293 | "max_height": null, | 8074 | "max_height": null, |
| 7294 | "max_width": null, | 8075 | "max_width": null, |
| ... | @@ -7299,24 +8080,29 @@ | ... | @@ -7299,24 +8080,29 @@ |
| 7299 | }, | 8080 | }, |
| 7300 | "outlines": [ | 8081 | "outlines": [ |
| 7301 | { | 8082 | { |
| 7302 | "id": "{CCBB5969-BCE8-498B-BE1A-98FD3A44DB1A},14", | 8083 | "id": "{F721C9D5-9A6A-405F-94C9-9B85A7EEB980},14", |
| 7303 | "indents": [], | 8084 | "indents": [ |
| 8085 | 18.0, | ||
| 8086 | 0.0, | ||
| 8087 | 27.0, | ||
| 8088 | 27.0 | ||
| 8089 | ], | ||
| 7304 | "layout": { | 8090 | "layout": { |
| 7305 | "max_height": null, | 8091 | "max_height": 21.600000381469727, |
| 7306 | "max_width": null, | 8092 | "max_width": null, |
| 7307 | "reserved_width": null, | 8093 | "reserved_width": null, |
| 7308 | "width_set_by_user": null, | 8094 | "width_set_by_user": null, |
| 7309 | "x": null, | 8095 | "x": null, |
| 7310 | "y": null | 8096 | "y": null |
| 7311 | }, | 8097 | }, |
| 7312 | "min_width": null, | 8098 | "min_width": 162.0, |
| 7313 | "paragraphs": [ | 8099 | "paragraphs": [ |
| 7314 | { | 8100 | { |
| 7315 | "collapsed": false, | 8101 | "collapsed": false, |
| 7316 | "content": { | 8102 | "content": { |
| 7317 | "Text": { | 8103 | "Text": { |
| 7318 | "date_field": null, | 8104 | "date_field": null, |
| 7319 | "id": "{CCBB5969-BCE8-498B-BE1A-98FD3A44DB1A},16", | 8105 | "id": "{F721C9D5-9A6A-405F-94C9-9B85A7EEB980},16", |
| 7320 | "tags": [], | 8106 | "tags": [], |
| 7321 | "text": { | 8107 | "text": { |
| 7322 | "spans": [ | 8108 | "spans": [ |
| ... | @@ -7338,6 +8124,7 @@ | ... | @@ -7338,6 +8124,7 @@ |
| 7338 | "line_spacing": null, | 8124 | "line_spacing": null, |
| 7339 | "list_spacing": null, | 8125 | "list_spacing": null, |
| 7340 | "math": null, | 8126 | "math": null, |
| 8127 | "math_object": null, | ||
| 7341 | "rtl": null, | 8128 | "rtl": null, |
| 7342 | "space_after": null, | 8129 | "space_after": null, |
| 7343 | "space_before": null, | 8130 | "space_before": null, |
| ... | @@ -7368,6 +8155,7 @@ | ... | @@ -7368,6 +8155,7 @@ |
| 7368 | "line_spacing": null, | 8155 | "line_spacing": null, |
| 7369 | "list_spacing": null, | 8156 | "list_spacing": null, |
| 7370 | "math": null, | 8157 | "math": null, |
| 8158 | "math_object": null, | ||
| 7371 | "rtl": null, | 8159 | "rtl": null, |
| 7372 | "space_after": null, | 8160 | "space_after": null, |
| 7373 | "space_before": null, | 8161 | "space_before": null, |
| ... | @@ -7376,15 +8164,19 @@ | ... | @@ -7376,15 +8164,19 @@ |
| 7376 | "superscript": null, | 8164 | "superscript": null, |
| 7377 | "underline": null | 8165 | "underline": null |
| 7378 | }, | 8166 | }, |
| 7379 | "id": "{CCBB5969-BCE8-498B-BE1A-98FD3A44DB1A},15", | 8167 | "id": "{F721C9D5-9A6A-405F-94C9-9B85A7EEB980},15", |
| 7380 | "level": 1, | 8168 | "level": 1, |
| 7381 | "lists": [], | 8169 | "lists": [], |
| 8170 | "media": { | ||
| 8171 | "recordings": [], | ||
| 8172 | "time_ms": null | ||
| 8173 | }, | ||
| 7382 | "parent": null, | 8174 | "parent": null, |
| 7383 | "style": "{CCBB5969-BCE8-498B-BE1A-98FD3A44DB1A},18", | 8175 | "style": "{F721C9D5-9A6A-405F-94C9-9B85A7EEB980},18", |
| 7384 | "tags": [] | 8176 | "tags": [] |
| 7385 | } | 8177 | } |
| 7386 | ], | 8178 | ], |
| 7387 | "title": false, | 8179 | "title": true, |
| 7388 | "unsupported": [] | 8180 | "unsupported": [] |
| 7389 | } | 8181 | } |
| 7390 | ] | 8182 | ] |
| ... | @@ -7395,7 +8187,7 @@ | ... | @@ -7395,7 +8187,7 @@ |
| 7395 | } | 8187 | } |
| 7396 | } | 8188 | } |
| 7397 | }, | 8189 | }, |
| 7398 | "space": "{CCBB5969-BCE8-498B-BE1A-98FD3A44DB1A},1" | 8190 | "space": "{F721C9D5-9A6A-405F-94C9-9B85A7EEB980},1" |
| 7399 | } | 8191 | } |
| 7400 | ] | 8192 | ] |
| 7401 | ], | 8193 | ], |
corpus/page-lifecycle/offline-edits/twelve-clients/candidate/pages.one| Binary files a/corpus/page-lifecycle/offline-edits/twelve-clients/candidate/pages.one and b/corpus/page-lifecycle/offline-edits/twelve-clients/candidate/pages.one differ | |||
corpus/page-lifecycle/offline-edits/twelve-clients/cold/commands.jsonl+1-1| ... | @@ -1,3 +1,3 @@ | ... | @@ -1,3 +1,3 @@ |
| 1 | {"command": "powershell -NoProfile -Command \"Expand-Archive -LiteralPath C:\\one-tests\\transfer.zip -DestinationPath C:\\one-tests\\runs\\capture\\notebook\"", "exit": 0, "stdout": "", "stderr": "", "error": null} | 1 | {"command": "powershell -NoProfile -Command \"Expand-Archive -LiteralPath C:\\one-tests\\transfer.zip -DestinationPath C:\\one-tests\\runs\\capture\\notebook\"", "exit": 0, "stdout": "", "stderr": "", "error": null} |
| 2 | {"command": "powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File C:\\one-tests\\read-current.ps1 -Root C:\\one-tests\\runs\\capture -CloneHost ONE-M6-16C2E4D4 -ExpectedPages 37", "exit": 0, "stdout": "Read 1 sections and 37 pages.\r\n", "stderr": "", "error": null} | 2 | {"command": "powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File C:\\one-tests\\read-current.ps1 -Root C:\\one-tests\\runs\\capture -CloneHost ONE-M6-8EABB42E -ExpectedPages 37", "exit": 0, "stdout": "Read 1 sections and 37 pages.\r\n", "stderr": "", "error": null} |
| 3 | {"command": "powershell -NoProfile -Command \"Compress-Archive -Force -Path C:\\one-tests\\runs\\capture\\* -DestinationPath C:\\one-tests\\captured.zip\"", "exit": 0, "stdout": "", "stderr": "", "error": null} | 3 | {"command": "powershell -NoProfile -Command \"Compress-Archive -Force -Path C:\\one-tests\\runs\\capture\\* -DestinationPath C:\\one-tests\\captured.zip\"", "exit": 0, "stdout": "", "stderr": "", "error": null} |
corpus/page-lifecycle/offline-edits/twelve-clients/cold/machine.json+1-1| ... | @@ -1 +1 @@ | ... | @@ -1 +1 @@ |
| 1 | {"name": "m6-16c2e4d4", "hostname": "ONE-M6-16C2E4D4"} | 1 | {"name": "m6-8eabb42e", "hostname": "ONE-M6-8EABB42E"} |
corpus/page-lifecycle/offline-edits/twelve-clients/cold/notebook/Open Notebook.onetoc2| Binary files a/corpus/page-lifecycle/offline-edits/twelve-clients/cold/notebook/Open Notebook.onetoc2 and b/corpus/page-lifecycle/offline-edits/twelve-clients/cold/notebook/Open Notebook.onetoc2 differ | |||
corpus/page-lifecycle/offline-edits/twelve-clients/cold/notebook/pages.one| Binary files a/corpus/page-lifecycle/offline-edits/twelve-clients/cold/notebook/pages.one and b/corpus/page-lifecycle/offline-edits/twelve-clients/cold/notebook/pages.one differ | |||
corpus/page-lifecycle/offline-edits/twelve-clients/cold/read/environment.json+1-1| ... | @@ -1,7 +1,7 @@ | ... | @@ -1,7 +1,7 @@ |
| 1 | { | 1 | { |
| 2 | "powershell": "5.1.14409.1005", | 2 | "powershell": "5.1.14409.1005", |
| 3 | "schema": "xs2010", | 3 | "schema": "xs2010", |
| 4 | "hostname": "ONE-M6-16C2E4D4", | 4 | "hostname": "ONE-M6-8EABB42E", |
| 5 | "cold": true, | 5 | "cold": true, |
| 6 | "onenote": "14.0.4763.1000" | 6 | "onenote": "14.0.4763.1000" |
| 7 | } | 7 | } |
corpus/page-lifecycle/offline-edits/twelve-clients/cold/read/hierarchy.xml+1-1| ... | @@ -1,2 +1,2 @@ | ... | @@ -1,2 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Notebook xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" name="notebook" nickname="notebook" ID="{DBBB79D4-5B5D-4081-818D-136DFB209E97}{1}{B0}" path="C:\one-tests\runs\capture\notebook" lastModifiedTime="2026-09-11T10:55:58.000Z" color="#B49EDE"><one:Section name="pages" ID="{E215CFF8-B98E-0DF4-196B-B38D8F97EB71}{1}{B0}" path="C:\one-tests\runs\capture\notebook\pages.one" lastModifiedTime="2026-09-11T10:55:58.000Z" color="#8AA8E4"><one:Page ID="{F4744DDD-E17E-0100-3BAC-98BC70CCC100}{1}{B0}" name="Sentinel" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="1"/><one:Page ID="{7C71FA76-38AF-07A9-264C-8971C4190282}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="1"/><one:Page ID="{C25C2B60-8E25-0382-1BCE-4099132AEA06}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="1"/><one:Page ID="{BADEB4C7-7F75-0B8E-19CA-C332C7A7875B}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="2"/><one:Page ID="{2501211E-A8B9-0EC3-18C9-AF5B02AD7C06}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="1"/><one:Page ID="{FB3175D5-307C-0E50-1607-1F154F5E4118}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="1"/><one:Page ID="{8507D83B-3994-0B29-08D6-E26999709ECD}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="2"/><one:Page ID="{99D05405-7D75-0D65-21A4-09E30FDCAFE8}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="1"/><one:Page ID="{44F31BFD-6543-00F6-3BB5-148B33556539}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="1"/><one:Page ID="{B06E19F3-05FC-0B60-3287-C13C24C193CE}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="2"/><one:Page ID="{9F7E7D29-16F6-0439-1415-8CE74E75CA89}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="1"/><one:Page ID="{6CE577C5-BDE9-0122-2D12-C1E061D8EFD6}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="1"/><one:Page ID="{652A14A0-FE09-05CB-3095-99BFA255E1FD}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="2"/><one:Page ID="{CFE7E2AC-C22F-0D7C-18D3-601E40CC312C}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="1"/><one:Page ID="{3EB6AEAF-6C68-088B-0171-3D83AAE36EB8}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="1"/><one:Page ID="{1D03640E-9B71-05FC-271B-1026DCC029B1}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="2"/><one:Page ID="{0C795549-BF3B-0480-39C3-C878E2BF5C53}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="1"/><one:Page ID="{1A48F415-F519-056D-00E4-9960FF36983E}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="1"/><one:Page ID="{9D4E6F5A-05B8-0EAD-18A3-62FF3663F86C}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="2"/><one:Page ID="{B7E2E308-7768-044A-155D-A0D7ED14E4D2}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="1"/><one:Page ID="{F54516CD-DE85-081D-1761-468628E72D1B}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="1"/><one:Page ID="{8F641324-4B67-0ACD-0A4E-2CB8CCC6C1E0}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="2"/><one:Page ID="{0D8E1647-4657-0CB4-2F3A-94D4CC5845A0}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="1"/><one:Page ID="{A008EAA3-7B7D-0618-1E1D-41C8CD7B2DC1}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="1"/><one:Page ID="{1A655A55-0FD5-025E-142E-2C89D842D58D}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="2"/><one:Page ID="{A0F068A1-5642-022B-2681-F2126BB7C3BD}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="1"/><one:Page ID="{07A6BDA3-4B12-0AD9-085A-3AAEA3257787}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="1"/><one:Page ID="{F5512F5C-2626-00AA-0B5F-EA6BD4164873}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:49.000Z" pageLevel="2"/><one:Page ID="{328F9323-76C9-06C0-284F-C287017C4F27}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:49.000Z" pageLevel="1"/><one:Page ID="{803FBA4F-17CA-00CB-348A-86A900FBE117}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:49.000Z" pageLevel="1"/><one:Page ID="{6D9AABC2-10E2-0686-29D8-679A327568A0}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:49.000Z" pageLevel="2"/><one:Page ID="{6A77B3B0-3133-09A1-382F-F28D4685D5F7}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:49.000Z" pageLevel="1"/><one:Page ID="{9B5420CB-C546-0239-2C6C-E3632B133D5F}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:49.000Z" pageLevel="1"/><one:Page ID="{73964C75-7738-03A7-146C-BF21A734E497}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:49.000Z" pageLevel="2"/><one:Page ID="{BE524D9D-33C8-0650-138F-4C17DDF5CE5D}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:49.000Z" pageLevel="1"/><one:Page ID="{BFE40300-AAF8-0FAD-3762-93332A5EDA4D}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:49.000Z" pageLevel="1"/><one:Page ID="{599A7FED-A2BC-01AD-0AA7-9367B48C643D}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:49.000Z" pageLevel="2"/></one:Section></one:Notebook> | 2 | <one:Notebook xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" name="notebook" nickname="notebook" ID="{885E80BA-2527-46AB-B60B-807DE2E83A73}{1}{B0}" path="C:\one-tests\runs\capture\notebook" lastModifiedTime="2026-09-26T06:36:34.000Z" color="#B49EDE"><one:Section name="pages" ID="{7FD5C268-3371-0110-1F8E-BEADD565CD56}{1}{B0}" path="C:\one-tests\runs\capture\notebook\pages.one" lastModifiedTime="2026-09-26T06:36:34.000Z" color="#8AA8E4"><one:Page ID="{D14084F4-7B9A-0878-09E5-EECC19CBE48C}{1}{B0}" name="Sentinel" dateTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:36:24.000Z" pageLevel="1"/><one:Page ID="{00883EEB-4E0B-07B5-0F54-872BA13B23CD}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:36:24.000Z" pageLevel="1"/><one:Page ID="{AE3E419C-38CB-0069-07DE-E9440F759DD9}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:36:24.000Z" pageLevel="1"/><one:Page ID="{510102C7-04CF-0D15-313A-77B521E237B5}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="2"/><one:Page ID="{EB88C82F-EF38-01AD-179F-DEB37531C674}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="1"/><one:Page ID="{F297A189-463C-0A06-2B9E-B530B4CB073E}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="1"/><one:Page ID="{75BA6420-3E05-0502-08FB-EFEEA8AD892C}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="2"/><one:Page ID="{80C417D9-7982-0C0E-1D8F-DD9227214F3B}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="1"/><one:Page ID="{733A3554-E782-0C2E-06C7-8AAA271FBEDB}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="1"/><one:Page ID="{28651488-20CB-08C4-1DEF-0FA8BEF2F880}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="2"/><one:Page ID="{E4E1032E-9934-08BF-0708-2B5E96C12317}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="1"/><one:Page ID="{CDF19F1C-24C3-0001-3E1C-1678A7109AB8}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="1"/><one:Page ID="{D74171A0-BC7D-0BF6-2552-24E365CC9542}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="2"/><one:Page ID="{8F6A4BE1-0427-09A3-0794-95BDA8B83676}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="1"/><one:Page ID="{74FE5D8C-5D74-0635-0154-BCF70F757132}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="1"/><one:Page ID="{118F4150-140B-05FF-27ED-BEB8F6B3C54D}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="2"/><one:Page ID="{D51DD688-8EA1-0E9D-04CE-DE0DAEB0A978}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="1"/><one:Page ID="{98766D42-CDF7-0A17-26DC-093AC97DACF6}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="1"/><one:Page ID="{23AD5CEF-76D6-0B91-2089-9119CA13055A}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="2"/><one:Page ID="{6A4CFAEA-7E3C-0B1A-2C5D-15AA893FC833}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="1"/><one:Page ID="{99ECED36-B3CC-0AAF-242A-EA4F5D619FE9}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="1"/><one:Page ID="{7E2EA9A2-C86B-0B65-24DA-8368D0422FAF}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="2"/><one:Page ID="{1E88E232-62C3-069F-2D94-4F60BB58CA16}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="1"/><one:Page ID="{94A62138-ABE0-01A5-01C1-3D3A88E6DDBA}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="1"/><one:Page ID="{495C8EA9-2B2A-0E7B-3091-074B0C179117}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="2"/><one:Page ID="{0A328779-8269-0BCF-22D2-4737ED5D4F37}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="1"/><one:Page ID="{41023EBE-33C9-0DFE-387A-C9BA7F05D1EC}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="1"/><one:Page ID="{86D3A849-9A80-0B46-3ED9-9493E5C562E7}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="2"/><one:Page ID="{1C922EB2-4250-07E4-34C9-234F794943F7}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="1"/><one:Page ID="{0C48DE55-D5B7-0479-1BF2-4C17A0F30B1F}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="1"/><one:Page ID="{049B54CD-DC51-0E31-0BF8-F326C40B1026}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:36:26.000Z" pageLevel="2"/><one:Page ID="{5386195B-49C9-0A50-0FBF-A396B030F141}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:36:26.000Z" pageLevel="1"/><one:Page ID="{44017B3B-F099-06A5-3BE9-A822A2E81F34}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:36:26.000Z" pageLevel="1"/><one:Page ID="{DAC6618E-48C9-020D-15C8-9AC4EF182733}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:36:26.000Z" pageLevel="2"/><one:Page ID="{2D56A403-B4EA-00DF-2180-3FFAEE1E1024}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:36:26.000Z" pageLevel="1"/><one:Page ID="{829EF954-5F8A-09B8-39B2-2CDE8049EF6D}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:36:26.000Z" pageLevel="1"/><one:Page ID="{CFD331D7-8768-0262-172E-6383AED828FA}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:36:26.000Z" pageLevel="2"/></one:Section></one:Notebook> |
corpus/page-lifecycle/offline-edits/twelve-clients/cold/read/page-000.xml+1-1| ... | @@ -1,2 +1,2 @@ | ... | @@ -1,2 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{07A6BDA3-4B12-0AD9-085A-3AAEA3257787}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:54:16.000Z" objectID="{07A6BDA3-4B12-0AD9-085A-3AAEA3257787}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{00883EEB-4E0B-07B5-0F54-872BA13B23CD}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:36:24.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:33:09.000Z" objectID="{00883EEB-4E0B-07B5-0F54-872BA13B23CD}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-26T06:33:11.000Z" objectID="{4B40F130-BF5A-083A-064F-D39B8740C112}{2}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="115.1704635620117" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-26T06:33:11.000Z" lastModifiedTime="2026-09-26T06:33:11.000Z" objectID="{4B40F130-BF5A-083A-064F-D39B8740C112}{3}{B0}" alignment="left"><one:T><![CDATA[Actor 0 &#129408; é]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/page-lifecycle/offline-edits/twelve-clients/cold/read/page-001.xml+1-1| ... | @@ -1,2 +1,2 @@ | ... | @@ -1,2 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{0C795549-BF3B-0480-39C3-C878E2BF5C53}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:54:16.000Z" objectID="{0C795549-BF3B-0480-39C3-C878E2BF5C53}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-11T10:54:20.000Z" objectID="{EF89556A-7329-0A9C-27D0-846938399E56}{0}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="115.1704635620117" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-11T10:54:20.000Z" lastModifiedTime="2026-09-11T10:54:20.000Z" objectID="{DF8510A6-B246-0D04-25F0-72930FBA2A42}{0}{B0}" alignment="left"><one:T><![CDATA[Actor 5 &#129408; é]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{049B54CD-DC51-0E31-0BF8-F326C40B1026}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:36:26.000Z" pageLevel="2"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:33:10.000Z" objectID="{049B54CD-DC51-0E31-0BF8-F326C40B1026}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page> |
corpus/page-lifecycle/offline-edits/twelve-clients/cold/read/page-002.xml+1-1| ... | @@ -1,2 +1,2 @@ | ... | @@ -1,2 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{0D8E1647-4657-0CB4-2F3A-94D4CC5845A0}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:54:16.000Z" objectID="{0D8E1647-4657-0CB4-2F3A-94D4CC5845A0}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-11T10:54:20.000Z" objectID="{8742DFDF-A6F3-0EC5-05A3-D642B0F8EA4E}{0}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="115.1704635620117" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-11T10:54:20.000Z" lastModifiedTime="2026-09-11T10:54:20.000Z" objectID="{AD1AAF2A-4E6D-02EA-2398-1F243E0A04D2}{0}{B0}" alignment="left"><one:T><![CDATA[Actor 7 &#129408; é]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{0A328779-8269-0BCF-22D2-4737ED5D4F37}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:33:10.000Z" objectID="{0A328779-8269-0BCF-22D2-4737ED5D4F37}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-26T06:33:13.000Z" objectID="{4B40F130-BF5A-083A-064F-D39B8740C112}{26}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="115.1704635620117" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-26T06:33:13.000Z" lastModifiedTime="2026-09-26T06:33:13.000Z" objectID="{4B40F130-BF5A-083A-064F-D39B8740C112}{27}{B0}" alignment="left"><one:T><![CDATA[Actor 8 &#129408; é]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/page-lifecycle/offline-edits/twelve-clients/cold/read/page-003.xml+1-1| ... | @@ -1,2 +1,2 @@ | ... | @@ -1,2 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{1A48F415-F519-056D-00E4-9960FF36983E}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:54:16.000Z" objectID="{1A48F415-F519-056D-00E4-9960FF36983E}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{0C48DE55-D5B7-0479-1BF2-4C17A0F30B1F}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:33:10.000Z" objectID="{0C48DE55-D5B7-0479-1BF2-4C17A0F30B1F}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page> |
corpus/page-lifecycle/offline-edits/twelve-clients/cold/read/page-004.xml+1-1| ... | @@ -1,2 +1,2 @@ | ... | @@ -1,2 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{1A655A55-0FD5-025E-142E-2C89D842D58D}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="2"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:54:16.000Z" objectID="{1A655A55-0FD5-025E-142E-2C89D842D58D}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{118F4150-140B-05FF-27ED-BEB8F6B3C54D}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="2"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:33:09.000Z" objectID="{118F4150-140B-05FF-27ED-BEB8F6B3C54D}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page> |
corpus/page-lifecycle/offline-edits/twelve-clients/cold/read/page-005.xml+1-1| ... | @@ -1,2 +1,2 @@ | ... | @@ -1,2 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{1D03640E-9B71-05FC-271B-1026DCC029B1}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="2"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:54:16.000Z" objectID="{1D03640E-9B71-05FC-271B-1026DCC029B1}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{1C922EB2-4250-07E4-34C9-234F794943F7}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:33:10.000Z" objectID="{1C922EB2-4250-07E4-34C9-234F794943F7}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-26T06:33:13.000Z" objectID="{4B40F130-BF5A-083A-064F-D39B8740C112}{29}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="115.1704635620117" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-26T06:33:13.000Z" lastModifiedTime="2026-09-26T06:33:13.000Z" objectID="{4B40F130-BF5A-083A-064F-D39B8740C112}{30}{B0}" alignment="left"><one:T><![CDATA[Actor 9 &#129408; é]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/page-lifecycle/offline-edits/twelve-clients/cold/read/page-006.xml+1-1| ... | @@ -1,2 +1,2 @@ | ... | @@ -1,2 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{2501211E-A8B9-0EC3-18C9-AF5B02AD7C06}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:54:16.000Z" objectID="{2501211E-A8B9-0EC3-18C9-AF5B02AD7C06}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-11T10:54:19.000Z" objectID="{7AFE6DBE-F13B-0E34-0584-741CB950DBDE}{0}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="115.1704635620117" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-11T10:54:19.000Z" lastModifiedTime="2026-09-11T10:54:19.000Z" objectID="{FA430EA8-AF00-0D92-12B0-B63F53246981}{0}{B0}" alignment="left"><one:T><![CDATA[Actor 1 &#129408; é]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{1E88E232-62C3-069F-2D94-4F60BB58CA16}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:33:10.000Z" objectID="{1E88E232-62C3-069F-2D94-4F60BB58CA16}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-26T06:33:13.000Z" objectID="{4B40F130-BF5A-083A-064F-D39B8740C112}{23}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="115.1704635620117" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-26T06:33:13.000Z" lastModifiedTime="2026-09-26T06:33:13.000Z" objectID="{4B40F130-BF5A-083A-064F-D39B8740C112}{24}{B0}" alignment="left"><one:T><![CDATA[Actor 7 &#129408; é]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/page-lifecycle/offline-edits/twelve-clients/cold/read/page-007.xml+1-1| ... | @@ -1,2 +1,2 @@ | ... | @@ -1,2 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{328F9323-76C9-06C0-284F-C287017C4F27}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:49.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:54:16.000Z" objectID="{328F9323-76C9-06C0-284F-C287017C4F27}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-11T10:54:21.000Z" objectID="{1B797AC3-D315-00F4-1ADA-613EBEE166DC}{0}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="115.1704635620117" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-11T10:54:21.000Z" lastModifiedTime="2026-09-11T10:54:21.000Z" objectID="{8D005AE0-4D81-036A-164E-D998E22D1FDA}{0}{B0}" alignment="left"><one:T><![CDATA[Actor 9 &#129408; é]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{23AD5CEF-76D6-0B91-2089-9119CA13055A}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="2"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:33:09.000Z" objectID="{23AD5CEF-76D6-0B91-2089-9119CA13055A}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page> |
corpus/page-lifecycle/offline-edits/twelve-clients/cold/read/page-008.xml+1-1| ... | @@ -1,2 +1,2 @@ | ... | @@ -1,2 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{3EB6AEAF-6C68-088B-0171-3D83AAE36EB8}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:54:16.000Z" objectID="{3EB6AEAF-6C68-088B-0171-3D83AAE36EB8}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{28651488-20CB-08C4-1DEF-0FA8BEF2F880}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="2"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:33:09.000Z" objectID="{28651488-20CB-08C4-1DEF-0FA8BEF2F880}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page> |
corpus/page-lifecycle/offline-edits/twelve-clients/cold/read/page-009.xml+1-1| ... | @@ -1,2 +1,2 @@ | ... | @@ -1,2 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{44F31BFD-6543-00F6-3BB5-148B33556539}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:54:16.000Z" objectID="{44F31BFD-6543-00F6-3BB5-148B33556539}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{2D56A403-B4EA-00DF-2180-3FFAEE1E1024}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:36:26.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:33:10.000Z" objectID="{2D56A403-B4EA-00DF-2180-3FFAEE1E1024}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-26T06:33:14.000Z" objectID="{4B40F130-BF5A-083A-064F-D39B8740C112}{35}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="120.7456665039062" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-26T06:33:14.000Z" lastModifiedTime="2026-09-26T06:33:14.000Z" objectID="{4B40F130-BF5A-083A-064F-D39B8740C112}{36}{B0}" alignment="left"><one:T><![CDATA[Actor 11 &#129408; é]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/page-lifecycle/offline-edits/twelve-clients/cold/read/page-010.xml+1-1| ... | @@ -1,2 +1,2 @@ | ... | @@ -1,2 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{599A7FED-A2BC-01AD-0AA7-9367B48C643D}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:49.000Z" pageLevel="2"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:54:16.000Z" objectID="{599A7FED-A2BC-01AD-0AA7-9367B48C643D}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{41023EBE-33C9-0DFE-387A-C9BA7F05D1EC}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:33:10.000Z" objectID="{41023EBE-33C9-0DFE-387A-C9BA7F05D1EC}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page> |
corpus/page-lifecycle/offline-edits/twelve-clients/cold/read/page-011.xml+1-1| ... | @@ -1,2 +1,2 @@ | ... | @@ -1,2 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{652A14A0-FE09-05CB-3095-99BFA255E1FD}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="2"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:54:16.000Z" objectID="{652A14A0-FE09-05CB-3095-99BFA255E1FD}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{44017B3B-F099-06A5-3BE9-A822A2E81F34}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:36:26.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:33:10.000Z" objectID="{44017B3B-F099-06A5-3BE9-A822A2E81F34}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page> |
corpus/page-lifecycle/offline-edits/twelve-clients/cold/read/page-012.xml+1-1| ... | @@ -1,2 +1,2 @@ | ... | @@ -1,2 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{6A77B3B0-3133-09A1-382F-F28D4685D5F7}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:49.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:54:16.000Z" objectID="{6A77B3B0-3133-09A1-382F-F28D4685D5F7}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-11T10:54:21.000Z" objectID="{D2B4E363-F08A-0A48-0C61-AADF669B4710}{0}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="120.7456665039062" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-11T10:54:21.000Z" lastModifiedTime="2026-09-11T10:54:21.000Z" objectID="{A56AAE48-5EF5-0A72-2B6D-C58364B59DBB}{0}{B0}" alignment="left"><one:T><![CDATA[Actor 10 &#129408; é]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{495C8EA9-2B2A-0E7B-3091-074B0C179117}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="2"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:33:10.000Z" objectID="{495C8EA9-2B2A-0E7B-3091-074B0C179117}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page> |
corpus/page-lifecycle/offline-edits/twelve-clients/cold/read/page-013.xml+1-1| ... | @@ -1,2 +1,2 @@ | ... | @@ -1,2 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{6CE577C5-BDE9-0122-2D12-C1E061D8EFD6}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:54:16.000Z" objectID="{6CE577C5-BDE9-0122-2D12-C1E061D8EFD6}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{510102C7-04CF-0D15-313A-77B521E237B5}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="2"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:33:09.000Z" objectID="{510102C7-04CF-0D15-313A-77B521E237B5}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page> |
corpus/page-lifecycle/offline-edits/twelve-clients/cold/read/page-014.xml+1-1| ... | @@ -1,2 +1,2 @@ | ... | @@ -1,2 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{6D9AABC2-10E2-0686-29D8-679A327568A0}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:49.000Z" pageLevel="2"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:54:16.000Z" objectID="{6D9AABC2-10E2-0686-29D8-679A327568A0}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{5386195B-49C9-0A50-0FBF-A396B030F141}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:36:26.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:33:10.000Z" objectID="{5386195B-49C9-0A50-0FBF-A396B030F141}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-26T06:33:14.000Z" objectID="{4B40F130-BF5A-083A-064F-D39B8740C112}{32}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="120.7456665039062" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-26T06:33:14.000Z" lastModifiedTime="2026-09-26T06:33:14.000Z" objectID="{4B40F130-BF5A-083A-064F-D39B8740C112}{33}{B0}" alignment="left"><one:T><![CDATA[Actor 10 &#129408; é]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/page-lifecycle/offline-edits/twelve-clients/cold/read/page-015.xml+1-1| ... | @@ -1,2 +1,2 @@ | ... | @@ -1,2 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{73964C75-7738-03A7-146C-BF21A734E497}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:49.000Z" pageLevel="2"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:54:16.000Z" objectID="{73964C75-7738-03A7-146C-BF21A734E497}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{6A4CFAEA-7E3C-0B1A-2C5D-15AA893FC833}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:33:09.000Z" objectID="{6A4CFAEA-7E3C-0B1A-2C5D-15AA893FC833}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-26T06:33:13.000Z" objectID="{4B40F130-BF5A-083A-064F-D39B8740C112}{20}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="115.1704635620117" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-26T06:33:13.000Z" lastModifiedTime="2026-09-26T06:33:13.000Z" objectID="{4B40F130-BF5A-083A-064F-D39B8740C112}{21}{B0}" alignment="left"><one:T><![CDATA[Actor 6 &#129408; é]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/page-lifecycle/offline-edits/twelve-clients/cold/read/page-016.xml+1-1| ... | @@ -1,2 +1,2 @@ | ... | @@ -1,2 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{7C71FA76-38AF-07A9-264C-8971C4190282}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:54:16.000Z" objectID="{7C71FA76-38AF-07A9-264C-8971C4190282}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-11T10:54:19.000Z" objectID="{47FED148-3B2B-0A3D-2EB1-6E53A78E6A7C}{0}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="115.1704635620117" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-11T10:54:19.000Z" lastModifiedTime="2026-09-11T10:54:19.000Z" objectID="{6869F737-972F-0D70-14D3-CEDCB8B52D61}{0}{B0}" alignment="left"><one:T><![CDATA[Actor 0 &#129408; é]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{733A3554-E782-0C2E-06C7-8AAA271FBEDB}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:33:09.000Z" objectID="{733A3554-E782-0C2E-06C7-8AAA271FBEDB}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page> |
corpus/page-lifecycle/offline-edits/twelve-clients/cold/read/page-017.xml+1-1| ... | @@ -1,2 +1,2 @@ | ... | @@ -1,2 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{803FBA4F-17CA-00CB-348A-86A900FBE117}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:49.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:54:16.000Z" objectID="{803FBA4F-17CA-00CB-348A-86A900FBE117}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{74FE5D8C-5D74-0635-0154-BCF70F757132}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:33:09.000Z" objectID="{74FE5D8C-5D74-0635-0154-BCF70F757132}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page> |
corpus/page-lifecycle/offline-edits/twelve-clients/cold/read/page-018.xml+1-1| ... | @@ -1,2 +1,2 @@ | ... | @@ -1,2 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{8507D83B-3994-0B29-08D6-E26999709ECD}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="2"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:54:16.000Z" objectID="{8507D83B-3994-0B29-08D6-E26999709ECD}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{75BA6420-3E05-0502-08FB-EFEEA8AD892C}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="2"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:33:09.000Z" objectID="{75BA6420-3E05-0502-08FB-EFEEA8AD892C}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page> |
corpus/page-lifecycle/offline-edits/twelve-clients/cold/read/page-019.xml+1-1| ... | @@ -1,2 +1,2 @@ | ... | @@ -1,2 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{8F641324-4B67-0ACD-0A4E-2CB8CCC6C1E0}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="2"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:54:16.000Z" objectID="{8F641324-4B67-0ACD-0A4E-2CB8CCC6C1E0}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{7E2EA9A2-C86B-0B65-24DA-8368D0422FAF}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="2"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:33:10.000Z" objectID="{7E2EA9A2-C86B-0B65-24DA-8368D0422FAF}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page> |
corpus/page-lifecycle/offline-edits/twelve-clients/cold/read/page-020.xml+1-1| ... | @@ -1,2 +1,2 @@ | ... | @@ -1,2 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{99D05405-7D75-0D65-21A4-09E30FDCAFE8}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:54:16.000Z" objectID="{99D05405-7D75-0D65-21A4-09E30FDCAFE8}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-11T10:54:19.000Z" objectID="{2EAE2E05-1122-0141-125D-4A765C039CA6}{0}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="115.1704635620117" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-11T10:54:19.000Z" lastModifiedTime="2026-09-11T10:54:19.000Z" objectID="{DF098EEC-501F-064E-12A4-00B964A47490}{0}{B0}" alignment="left"><one:T><![CDATA[Actor 2 &#129408; é]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{80C417D9-7982-0C0E-1D8F-DD9227214F3B}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:33:09.000Z" objectID="{80C417D9-7982-0C0E-1D8F-DD9227214F3B}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-26T06:33:12.000Z" objectID="{4B40F130-BF5A-083A-064F-D39B8740C112}{8}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="115.1704635620117" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-26T06:33:12.000Z" lastModifiedTime="2026-09-26T06:33:12.000Z" objectID="{4B40F130-BF5A-083A-064F-D39B8740C112}{9}{B0}" alignment="left"><one:T><![CDATA[Actor 2 &#129408; é]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/page-lifecycle/offline-edits/twelve-clients/cold/read/page-021.xml+1-1| ... | @@ -1,2 +1,2 @@ | ... | @@ -1,2 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{9B5420CB-C546-0239-2C6C-E3632B133D5F}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:49.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:54:16.000Z" objectID="{9B5420CB-C546-0239-2C6C-E3632B133D5F}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{829EF954-5F8A-09B8-39B2-2CDE8049EF6D}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:36:26.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:33:10.000Z" objectID="{829EF954-5F8A-09B8-39B2-2CDE8049EF6D}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page> |
corpus/page-lifecycle/offline-edits/twelve-clients/cold/read/page-022.xml+1-1| ... | @@ -1,2 +1,2 @@ | ... | @@ -1,2 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{9D4E6F5A-05B8-0EAD-18A3-62FF3663F86C}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="2"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:54:16.000Z" objectID="{9D4E6F5A-05B8-0EAD-18A3-62FF3663F86C}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{86D3A849-9A80-0B46-3ED9-9493E5C562E7}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="2"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:33:10.000Z" objectID="{86D3A849-9A80-0B46-3ED9-9493E5C562E7}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page> |
corpus/page-lifecycle/offline-edits/twelve-clients/cold/read/page-023.xml+1-1| ... | @@ -1,2 +1,2 @@ | ... | @@ -1,2 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{9F7E7D29-16F6-0439-1415-8CE74E75CA89}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:54:16.000Z" objectID="{9F7E7D29-16F6-0439-1415-8CE74E75CA89}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-11T10:54:19.000Z" objectID="{E1EC640A-8462-0A43-0B22-A003EB543393}{0}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="115.1704635620117" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-11T10:54:19.000Z" lastModifiedTime="2026-09-11T10:54:19.000Z" objectID="{D628EDB8-48F9-0341-2202-03A99842E1DE}{0}{B0}" alignment="left"><one:T><![CDATA[Actor 3 &#129408; é]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{8F6A4BE1-0427-09A3-0794-95BDA8B83676}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:33:09.000Z" objectID="{8F6A4BE1-0427-09A3-0794-95BDA8B83676}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-26T06:33:12.000Z" objectID="{4B40F130-BF5A-083A-064F-D39B8740C112}{14}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="115.1704635620117" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-26T06:33:12.000Z" lastModifiedTime="2026-09-26T06:33:12.000Z" objectID="{4B40F130-BF5A-083A-064F-D39B8740C112}{15}{B0}" alignment="left"><one:T><![CDATA[Actor 4 &#129408; é]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/page-lifecycle/offline-edits/twelve-clients/cold/read/page-024.xml+1-1| ... | @@ -1,2 +1,2 @@ | ... | @@ -1,2 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{A008EAA3-7B7D-0618-1E1D-41C8CD7B2DC1}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:54:16.000Z" objectID="{A008EAA3-7B7D-0618-1E1D-41C8CD7B2DC1}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{94A62138-ABE0-01A5-01C1-3D3A88E6DDBA}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:33:10.000Z" objectID="{94A62138-ABE0-01A5-01C1-3D3A88E6DDBA}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page> |
corpus/page-lifecycle/offline-edits/twelve-clients/cold/read/page-025.xml+1-1| ... | @@ -1,2 +1,2 @@ | ... | @@ -1,2 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{A0F068A1-5642-022B-2681-F2126BB7C3BD}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:54:16.000Z" objectID="{A0F068A1-5642-022B-2681-F2126BB7C3BD}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-11T10:54:21.000Z" objectID="{28ED0BBF-5CA7-05EE-2AB6-4BFC4F82B939}{0}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="115.1704635620117" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-11T10:54:21.000Z" lastModifiedTime="2026-09-11T10:54:21.000Z" objectID="{FBEFD32B-0691-0F3F-30D2-B893AB88EC33}{0}{B0}" alignment="left"><one:T><![CDATA[Actor 8 &#129408; é]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{98766D42-CDF7-0A17-26DC-093AC97DACF6}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:33:09.000Z" objectID="{98766D42-CDF7-0A17-26DC-093AC97DACF6}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page> |
corpus/page-lifecycle/offline-edits/twelve-clients/cold/read/page-026.xml+1-1| ... | @@ -1,2 +1,2 @@ | ... | @@ -1,2 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{B06E19F3-05FC-0B60-3287-C13C24C193CE}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="2"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:54:16.000Z" objectID="{B06E19F3-05FC-0B60-3287-C13C24C193CE}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{99ECED36-B3CC-0AAF-242A-EA4F5D619FE9}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:33:09.000Z" objectID="{99ECED36-B3CC-0AAF-242A-EA4F5D619FE9}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page> |
corpus/page-lifecycle/offline-edits/twelve-clients/cold/read/page-027.xml+1-1| ... | @@ -1,2 +1,2 @@ | ... | @@ -1,2 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{B7E2E308-7768-044A-155D-A0D7ED14E4D2}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:54:16.000Z" objectID="{B7E2E308-7768-044A-155D-A0D7ED14E4D2}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-11T10:54:20.000Z" objectID="{1B893290-060F-0AEC-366B-52E74AC0B0D2}{0}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="115.1704635620117" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-11T10:54:20.000Z" lastModifiedTime="2026-09-11T10:54:20.000Z" objectID="{896D454F-C999-0B2A-3ABA-71A912EE13F2}{0}{B0}" alignment="left"><one:T><![CDATA[Actor 6 &#129408; é]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{AE3E419C-38CB-0069-07DE-E9440F759DD9}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:36:24.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:33:09.000Z" objectID="{AE3E419C-38CB-0069-07DE-E9440F759DD9}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page> |
corpus/page-lifecycle/offline-edits/twelve-clients/cold/read/page-028.xml+1-1| ... | @@ -1,2 +1,2 @@ | ... | @@ -1,2 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{BADEB4C7-7F75-0B8E-19CA-C332C7A7875B}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="2"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:54:16.000Z" objectID="{BADEB4C7-7F75-0B8E-19CA-C332C7A7875B}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{CDF19F1C-24C3-0001-3E1C-1678A7109AB8}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:33:09.000Z" objectID="{CDF19F1C-24C3-0001-3E1C-1678A7109AB8}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page> |
corpus/page-lifecycle/offline-edits/twelve-clients/cold/read/page-029.xml+1-1| ... | @@ -1,2 +1,2 @@ | ... | @@ -1,2 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{BE524D9D-33C8-0650-138F-4C17DDF5CE5D}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:49.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:54:16.000Z" objectID="{BE524D9D-33C8-0650-138F-4C17DDF5CE5D}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-11T10:54:22.000Z" objectID="{720C4EA2-9C42-05CA-0146-F5FD5B7F74D9}{0}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="120.7456665039062" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-11T10:54:22.000Z" lastModifiedTime="2026-09-11T10:54:22.000Z" objectID="{05014EE3-11D5-03A2-1C8E-9C4F29CEF5BA}{0}{B0}" alignment="left"><one:T><![CDATA[Actor 11 &#129408; é]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{CFD331D7-8768-0262-172E-6383AED828FA}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:36:26.000Z" pageLevel="2"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:33:10.000Z" objectID="{CFD331D7-8768-0262-172E-6383AED828FA}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page> |
corpus/page-lifecycle/offline-edits/twelve-clients/cold/read/page-030.xml+1-1| ... | @@ -1,2 +1,2 @@ | ... | @@ -1,2 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{BFE40300-AAF8-0FAD-3762-93332A5EDA4D}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:49.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:54:16.000Z" objectID="{BFE40300-AAF8-0FAD-3762-93332A5EDA4D}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{D14084F4-7B9A-0878-09E5-EECC19CBE48C}{1}{B0}" name="Sentinel" dateTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:36:24.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-26T06:33:09.000Z" objectID="{7FD5C268-3371-0110-1F8E-BEADD565CD56}{23}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="103.0424346923828" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:33:09.000Z" objectID="{7FD5C268-3371-0110-1F8E-BEADD565CD56}{24}{B0}" alignment="left"><one:T><![CDATA[Sentinel]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/page-lifecycle/offline-edits/twelve-clients/cold/read/page-031.xml+1-1| ... | @@ -1,2 +1,2 @@ | ... | @@ -1,2 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{C25C2B60-8E25-0382-1BCE-4099132AEA06}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:54:16.000Z" objectID="{C25C2B60-8E25-0382-1BCE-4099132AEA06}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{D51DD688-8EA1-0E9D-04CE-DE0DAEB0A978}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:33:09.000Z" objectID="{D51DD688-8EA1-0E9D-04CE-DE0DAEB0A978}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-26T06:33:13.000Z" objectID="{4B40F130-BF5A-083A-064F-D39B8740C112}{17}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="115.1704635620117" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-26T06:33:13.000Z" lastModifiedTime="2026-09-26T06:33:13.000Z" objectID="{4B40F130-BF5A-083A-064F-D39B8740C112}{18}{B0}" alignment="left"><one:T><![CDATA[Actor 5 &#129408; é]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/page-lifecycle/offline-edits/twelve-clients/cold/read/page-032.xml+1-1| ... | @@ -1,2 +1,2 @@ | ... | @@ -1,2 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{CFE7E2AC-C22F-0D7C-18D3-601E40CC312C}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:54:16.000Z" objectID="{CFE7E2AC-C22F-0D7C-18D3-601E40CC312C}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-11T10:54:20.000Z" objectID="{18871A87-5D82-0359-1D21-3C498EEA581D}{0}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="115.1704635620117" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-11T10:54:20.000Z" lastModifiedTime="2026-09-11T10:54:20.000Z" objectID="{69F64A09-B1C8-0FEA-1327-17389D823403}{0}{B0}" alignment="left"><one:T><![CDATA[Actor 4 &#129408; é]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{D74171A0-BC7D-0BF6-2552-24E365CC9542}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="2"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:33:09.000Z" objectID="{D74171A0-BC7D-0BF6-2552-24E365CC9542}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page> |
corpus/page-lifecycle/offline-edits/twelve-clients/cold/read/page-033.xml+1-1| ... | @@ -1,2 +1,2 @@ | ... | @@ -1,2 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{F4744DDD-E17E-0100-3BAC-98BC70CCC100}{1}{B0}" name="Sentinel" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-11T10:54:16.000Z" objectID="{E215CFF8-B98E-0DF4-196B-B38D8F97EB71}{23}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="103.0424346923828" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:54:16.000Z" objectID="{E215CFF8-B98E-0DF4-196B-B38D8F97EB71}{24}{B0}" alignment="left"><one:T><![CDATA[Sentinel]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{DAC6618E-48C9-020D-15C8-9AC4EF182733}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:36:26.000Z" pageLevel="2"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:33:10.000Z" objectID="{DAC6618E-48C9-020D-15C8-9AC4EF182733}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page> |
corpus/page-lifecycle/offline-edits/twelve-clients/cold/read/page-034.xml+1-1| ... | @@ -1,2 +1,2 @@ | ... | @@ -1,2 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{F54516CD-DE85-081D-1761-468628E72D1B}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:54:16.000Z" objectID="{F54516CD-DE85-081D-1761-468628E72D1B}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{E4E1032E-9934-08BF-0708-2B5E96C12317}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:33:09.000Z" objectID="{E4E1032E-9934-08BF-0708-2B5E96C12317}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-26T06:33:12.000Z" objectID="{4B40F130-BF5A-083A-064F-D39B8740C112}{11}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="115.1704635620117" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-26T06:33:12.000Z" lastModifiedTime="2026-09-26T06:33:12.000Z" objectID="{4B40F130-BF5A-083A-064F-D39B8740C112}{12}{B0}" alignment="left"><one:T><![CDATA[Actor 3 &#129408; é]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/page-lifecycle/offline-edits/twelve-clients/cold/read/page-035.xml+1-1| ... | @@ -1,2 +1,2 @@ | ... | @@ -1,2 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{F5512F5C-2626-00AA-0B5F-EA6BD4164873}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:49.000Z" pageLevel="2"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:54:16.000Z" objectID="{F5512F5C-2626-00AA-0B5F-EA6BD4164873}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{EB88C82F-EF38-01AD-179F-DEB37531C674}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:33:09.000Z" objectID="{EB88C82F-EF38-01AD-179F-DEB37531C674}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-26T06:33:12.000Z" objectID="{4B40F130-BF5A-083A-064F-D39B8740C112}{5}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="115.1704635620117" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-26T06:33:12.000Z" lastModifiedTime="2026-09-26T06:33:12.000Z" objectID="{4B40F130-BF5A-083A-064F-D39B8740C112}{6}{B0}" alignment="left"><one:T><![CDATA[Actor 1 &#129408; é]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/page-lifecycle/offline-edits/twelve-clients/cold/read/page-036.xml+1-1| ... | @@ -1,2 +1,2 @@ | ... | @@ -1,2 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{FB3175D5-307C-0E50-1607-1F154F5E4118}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:54:16.000Z" objectID="{FB3175D5-307C-0E50-1607-1F154F5E4118}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{F297A189-463C-0A06-2B9E-B530B4CB073E}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:33:09.000Z" objectID="{F297A189-463C-0A06-2B9E-B530B4CB073E}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page> |
corpus/page-lifecycle/offline-edits/twelve-clients/cold/run.json+1-1| ... | @@ -1,5 +1,5 @@ | ... | @@ -1,5 +1,5 @@ |
| 1 | { | 1 | { |
| 2 | "notebook": "/private/tmp/m33/page-clients2/candidate", | 2 | "notebook": "/Users/clo/dev/one/evidence/titles/twelve-clients", |
| 3 | "expected_pages": 37, | 3 | "expected_pages": 37, |
| 4 | "author": null, | 4 | "author": null, |
| 5 | "author_timeout_seconds": 600, | 5 | "author_timeout_seconds": 600, |
corpus/page-lifecycle/offline-edits/twelve-clients/cold/source.json+9-3| ... | @@ -1,8 +1,14 @@ | ... | @@ -1,8 +1,14 @@ |
| 1 | [ | 1 | [ |
| 2 | { | ||
| 3 | "path": "manifest.json", | ||
| 4 | "bytes": 292907, | ||
| 5 | "sha256": "fea1d64b22da84d023d4c9014175f8e92a13acba099c7a1a3f8eae30d6c6407f", | ||
| 6 | "mtime_ns": 1790404395466294564 | ||
| 7 | }, | ||
| 2 | { | 8 | { |
| 3 | "path": "pages.one", | 9 | "path": "pages.one", |
| 4 | "bytes": 155344, | 10 | "bytes": 152360, |
| 5 | "sha256": "623ade8b5ee6fc837ae63d5293791039ecd17da9cb8bc5e243ea20cadf37958c", | 11 | "sha256": "302b6d2d31bc8f539a2217aed3391973678576b0aea1ce82c6e21760d6402e14", |
| 6 | "mtime_ns": 1789124063040528117 | 12 | "mtime_ns": 1790404395457867619 |
| 7 | } | 13 | } |
| 8 | ] | 14 | ] |
corpus/page-lifecycle/page-edits/stress/candidate/movement.one| Binary files a/corpus/page-lifecycle/page-edits/stress/candidate/movement.one and b/corpus/page-lifecycle/page-edits/stress/candidate/movement.one differ | |||
corpus/page-lifecycle/page-edits/stress/cold/notebook/Open Notebook.onetoc2| Binary files a/corpus/page-lifecycle/page-edits/stress/cold/notebook/Open Notebook.onetoc2 and b/corpus/page-lifecycle/page-edits/stress/cold/notebook/Open Notebook.onetoc2 differ | |||
corpus/page-lifecycle/page-edits/stress/cold/notebook/movement.one| Binary files a/corpus/page-lifecycle/page-edits/stress/cold/notebook/movement.one and b/corpus/page-lifecycle/page-edits/stress/cold/notebook/movement.one differ | |||
corpus/page-lifecycle/page-edits/stress/cold/read/environment.json+1-1| ... | @@ -1,7 +1,7 @@ | ... | @@ -1,7 +1,7 @@ |
| 1 | { | 1 | { |
| 2 | "powershell": "5.1.14409.1005", | 2 | "powershell": "5.1.14409.1005", |
| 3 | "schema": "xs2010", | 3 | "schema": "xs2010", |
| 4 | "hostname": "ONE-M6-40A088EC", | 4 | "hostname": "ONE-M6-02A94B20", |
| 5 | "cold": true, | 5 | "cold": true, |
| 6 | "onenote": "14.0.4763.1000" | 6 | "onenote": "14.0.4763.1000" |
| 7 | } | 7 | } |
corpus/page-lifecycle/page-edits/stress/cold/read/hierarchy.xml+1-1| ... | @@ -1,2 +1,2 @@ | ... | @@ -1,2 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Notebook xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" name="notebook" nickname="notebook" ID="{D322FB5E-1D61-4189-93EE-7E0AB4F8002F}{1}{B0}" path="C:\one-tests\runs\capture\notebook" lastModifiedTime="2026-09-09T06:44:35.000Z" color="#B49EDE"><one:Section name="movement" ID="{E9C7E704-C66E-0144-364E-95B272D68AF3}{1}{B0}" path="C:\one-tests\runs\capture\notebook\movement.one" lastModifiedTime="2026-09-09T06:44:35.000Z" color="#8AA8E4"><one:Page ID="{651B4683-48D9-0D97-27C7-D2F85B4E8F63}{1}{B0}" name="Original" dateTime="2026-09-09T06:39:25.000Z" lastModifiedTime="2026-09-09T06:44:35.000Z" pageLevel="1"/><one:Page ID="{83F7E375-3B9D-01C7-16E7-885AC0D5F9C5}{1}{B0}" name="Child" dateTime="2026-09-09T06:39:25.000Z" lastModifiedTime="2026-09-09T06:44:35.000Z" pageLevel="1"/></one:Section></one:Notebook> | 2 | <one:Notebook xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" name="notebook" nickname="notebook" ID="{F5F68BC6-5330-453A-AFAE-91BBA8C96783}{1}{B0}" path="C:\one-tests\runs\capture\notebook" lastModifiedTime="2026-09-26T06:39:47.000Z" color="#B49EDE"><one:Section name="movement" ID="{5E701DC7-30F2-0B02-006D-13258C6FBE79}{1}{B0}" path="C:\one-tests\runs\capture\notebook\movement.one" lastModifiedTime="2026-09-26T06:39:47.000Z" color="#8AA8E4"><one:Page ID="{D2EBE0FB-41A5-0CC9-1173-37795AF95DA7}{1}{B0}" name="Original" dateTime="2026-09-26T06:32:13.000Z" lastModifiedTime="2026-09-26T06:39:47.000Z" pageLevel="1"/><one:Page ID="{87328C7E-CC45-0126-1AE0-F144B8CE03BF}{1}{B0}" name="Child" dateTime="2026-09-26T06:32:13.000Z" lastModifiedTime="2026-09-26T06:39:47.000Z" pageLevel="1"/></one:Section></one:Notebook> |
corpus/page-lifecycle/page-edits/stress/cold/read/page-000.xml+1-1| ... | @@ -1,2 +1,2 @@ | ... | @@ -1,2 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{651B4683-48D9-0D97-27C7-D2F85B4E8F63}{1}{B0}" name="Original" dateTime="2026-09-09T06:39:25.000Z" lastModifiedTime="2026-09-09T06:44:35.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-09T06:39:25.000Z" objectID="{E9C7E704-C66E-0144-364E-95B272D68AF3}{23}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="101.66748046875" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-09T06:39:25.000Z" lastModifiedTime="2026-09-09T06:39:25.000Z" objectID="{E9C7E704-C66E-0144-364E-95B272D68AF3}{24}{B0}" alignment="left"><one:T><![CDATA[Original]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{87328C7E-CC45-0126-1AE0-F144B8CE03BF}{1}{B0}" name="Child" dateTime="2026-09-26T06:32:13.000Z" lastModifiedTime="2026-09-26T06:39:47.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-26T06:32:13.000Z" lastModifiedTime="2026-09-26T06:32:13.000Z" objectID="{87328C7E-CC45-0126-1AE0-F144B8CE03BF}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Child]]></one:T></one:OE></one:Title></one:Page> |
corpus/page-lifecycle/page-edits/stress/cold/read/page-001.xml+1-1| ... | @@ -1,2 +1,2 @@ | ... | @@ -1,2 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{83F7E375-3B9D-01C7-16E7-885AC0D5F9C5}{1}{B0}" name="Child" dateTime="2026-09-09T06:39:25.000Z" lastModifiedTime="2026-09-09T06:44:35.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-09T06:39:25.000Z" lastModifiedTime="2026-09-09T06:39:25.000Z" objectID="{83F7E375-3B9D-01C7-16E7-885AC0D5F9C5}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Child]]></one:T></one:OE></one:Title></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{D2EBE0FB-41A5-0CC9-1173-37795AF95DA7}{1}{B0}" name="Original" dateTime="2026-09-26T06:32:13.000Z" lastModifiedTime="2026-09-26T06:39:47.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-26T06:32:13.000Z" objectID="{5E701DC7-30F2-0B02-006D-13258C6FBE79}{23}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="101.66748046875" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-26T06:32:13.000Z" lastModifiedTime="2026-09-26T06:32:13.000Z" objectID="{5E701DC7-30F2-0B02-006D-13258C6FBE79}{24}{B0}" alignment="left"><one:T><![CDATA[Original]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/page-lifecycle/page-edits/stress/provenance.json+6-6| ... | @@ -1,10 +1,10 @@ | ... | @@ -1,10 +1,10 @@ |
| 1 | { | 1 | { |
| 2 | "generator": "crates/onestore/tests/page_movement.rs::repeated_nesting_retains_series_history_across_revision_checkpoints", | 2 | "generator": "crates/onestore/tests/page_movement.rs::repeated_nesting_retains_series_history_across_revision_checkpoints", |
| 3 | "edits": 520, | 3 | "edits": 520, |
| 4 | "capture": "evidence/m10/page-edit-stress-cold-01", | 4 | "capture": "evidence/titles/cold-stress", |
| 5 | "machine": { | 5 | "machine": { |
| 6 | "name": "m6-40a088ec", | 6 | "name": "m6-02a94b20", |
| 7 | "hostname": "ONE-M6-40A088EC" | 7 | "hostname": "ONE-M6-02A94B20" |
| 8 | }, | 8 | }, |
| 9 | "teardown": { | 9 | "teardown": { |
| 10 | "absent": true | 10 | "absent": true |
| ... | @@ -12,9 +12,9 @@ | ... | @@ -12,9 +12,9 @@ |
| 12 | "source": [ | 12 | "source": [ |
| 13 | { | 13 | { |
| 14 | "path": "movement.one", | 14 | "path": "movement.one", |
| 15 | "bytes": 2635536, | 15 | "bytes": 959216, |
| 16 | "sha256": "77dbec39ca25558b596b477d57117416f23ee133519f5179d7ebf1a33b38c248", | 16 | "sha256": "e2948ff3d01839a2c6a7908633739e97c4ebbe28e584c105bb1166669508758f", |
| 17 | "mtime_ns": 1788936082839470214 | 17 | "mtime_ns": 1790404366792422729 |
| 18 | } | 18 | } |
| 19 | ] | 19 | ] |
| 20 | } | 20 | } |
corpus/paragraph-edit/README.md+2-1| ... | @@ -28,7 +28,8 @@ when regenerating the corpus. | ... | @@ -28,7 +28,8 @@ when regenerating the corpus. |
| 28 | 28 | ||
| 29 | `rust-split` retains twelve `ParagraphSplit` outputs, their cold native captures, | 29 | `rust-split` retains twelve `ParagraphSplit` outputs, their cold native captures, |
| 30 | and a second native session typing into four empty boundary paragraphs. The | 30 | and a second native session typing into four empty boundary paragraphs. The |
| 31 | hyperlink case stays unchanged because this operation rejects fields. The Rust | 31 | hyperlink case has no output: the operation rejected fields when this row was |
| 32 | captured, and the writer now splits before a link as OneNote does. The Rust | ||
| 32 | test `export_native_paragraph_splits` generates candidate notebooks; its output | 33 | test `export_native_paragraph_splits` generates candidate notebooks; its output |
| 33 | directory comes from `ONESTORE_PARAGRAPH_OUTPUT`. Native character/style comparisons | 34 | directory comes from `ONESTORE_PARAGRAPH_OUTPUT`. Native character/style comparisons |
| 34 | use both the keyboard-generated controls and the Rust-written notebooks. Empty | 35 | use both the keyboard-generated controls and the Rust-written notebooks. Empty |
corpus/picture-edit/inserted/candidate/Open Notebook.onetoc2| Binary files a/corpus/picture-edit/inserted/candidate/Open Notebook.onetoc2 and b/corpus/picture-edit/inserted/candidate/Open Notebook.onetoc2 differ | |||
corpus/picture-edit/inserted/candidate/pictures.one| Binary files a/corpus/picture-edit/inserted/candidate/pictures.one and b/corpus/picture-edit/inserted/candidate/pictures.one differ | |||
corpus/picture-edit/inserted/cold/commands.jsonl+1-1| ... | @@ -1,3 +1,3 @@ | ... | @@ -1,3 +1,3 @@ |
| 1 | {"command": "powershell -NoProfile -Command \"Expand-Archive -LiteralPath C:\\one-tests\\transfer.zip -DestinationPath C:\\one-tests\\runs\\capture\\notebook\"", "exit": 0, "stdout": "", "stderr": "", "error": null} | 1 | {"command": "powershell -NoProfile -Command \"Expand-Archive -LiteralPath C:\\one-tests\\transfer.zip -DestinationPath C:\\one-tests\\runs\\capture\\notebook\"", "exit": 0, "stdout": "", "stderr": "", "error": null} |
| 2 | {"command": "powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File C:\\one-tests\\read-current.ps1 -Root C:\\one-tests\\runs\\capture -CloneHost ONE-M6-A5A8705B -ExpectedPages 1", "exit": 0, "stdout": "Read 1 sections and 1 pages.\r\n", "stderr": "", "error": null} | 2 | {"command": "powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File C:\\one-tests\\read-current.ps1 -Root C:\\one-tests\\runs\\capture -CloneHost ONE-M6-424D1706 -ExpectedPages 1", "exit": 0, "stdout": "Read 1 sections and 1 pages.\r\n", "stderr": "", "error": null} |
| 3 | {"command": "powershell -NoProfile -Command \"Compress-Archive -Force -Path C:\\one-tests\\runs\\capture\\* -DestinationPath C:\\one-tests\\captured.zip\"", "exit": 0, "stdout": "", "stderr": "", "error": null} | 3 | {"command": "powershell -NoProfile -Command \"Compress-Archive -Force -Path C:\\one-tests\\runs\\capture\\* -DestinationPath C:\\one-tests\\captured.zip\"", "exit": 0, "stdout": "", "stderr": "", "error": null} |
corpus/picture-edit/inserted/cold/machine.json+1-1| ... | @@ -1 +1 @@ | ... | @@ -1 +1 @@ |
| 1 | {"name": "m6-a5a8705b", "hostname": "ONE-M6-A5A8705B"} | 1 | {"name": "m6-424d1706", "hostname": "ONE-M6-424D1706"} |
corpus/picture-edit/inserted/cold/notebook/Open Notebook.onetoc2| Binary files a/corpus/picture-edit/inserted/cold/notebook/Open Notebook.onetoc2 and b/corpus/picture-edit/inserted/cold/notebook/Open Notebook.onetoc2 differ | |||
corpus/picture-edit/inserted/cold/notebook/pictures.one| Binary files a/corpus/picture-edit/inserted/cold/notebook/pictures.one and b/corpus/picture-edit/inserted/cold/notebook/pictures.one differ | |||
corpus/picture-edit/inserted/cold/read/environment.json+1-1| ... | @@ -1,7 +1,7 @@ | ... | @@ -1,7 +1,7 @@ |
| 1 | { | 1 | { |
| 2 | "powershell": "5.1.14409.1005", | 2 | "powershell": "5.1.14409.1005", |
| 3 | "schema": "xs2010", | 3 | "schema": "xs2010", |
| 4 | "hostname": "ONE-M6-A5A8705B", | 4 | "hostname": "ONE-M6-424D1706", |
| 5 | "cold": true, | 5 | "cold": true, |
| 6 | "onenote": "14.0.4763.1000" | 6 | "onenote": "14.0.4763.1000" |
| 7 | } | 7 | } |
corpus/picture-edit/inserted/cold/read/hierarchy.xml+1-1| ... | @@ -1,2 +1,2 @@ | ... | @@ -1,2 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Notebook xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" name="notebook" nickname="notebook" ID="{BB903F04-A992-47A1-B747-4D7C2D151005}{1}{B0}" path="C:\one-tests\runs\capture\notebook" lastModifiedTime="2026-09-12T20:08:56.000Z" color="#B49EDE"><one:Section name="pictures" ID="{54DE5097-F4BE-0BCC-0392-19FB4210AAAD}{1}{B0}" path="C:\one-tests\runs\capture\notebook\pictures.one" lastModifiedTime="2026-09-12T20:08:56.000Z" color="#8AA8E4"><one:Page ID="{D3A0AD9C-D12D-02B2-1683-4D32345076A5}{1}{B0}" name="Before the picture" dateTime="2026-09-12T20:07:15.000Z" lastModifiedTime="2026-09-12T20:08:56.000Z" pageLevel="1"/></one:Section></one:Notebook> | 2 | <one:Notebook xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" name="notebook" nickname="notebook" ID="{3A1D7F13-0843-4A0D-AB53-1EBAC00D091F}{1}{B0}" path="C:\one-tests\runs\capture\notebook" lastModifiedTime="2026-09-26T06:43:25.000Z" color="#B49EDE"><one:Section name="pictures" ID="{A1F8FA80-B0EC-0254-0FC4-F867B3DD72E7}{1}{B0}" path="C:\one-tests\runs\capture\notebook\pictures.one" lastModifiedTime="2026-09-26T06:43:25.000Z" color="#8AA8E4"><one:Page ID="{A3568077-FCA2-0E53-0B6A-615C7F205498}{1}{B0}" name="Before the picture" dateTime="2026-09-26T06:32:50.000Z" lastModifiedTime="2026-09-26T06:43:15.000Z" pageLevel="1"/></one:Section></one:Notebook> |
corpus/picture-edit/inserted/cold/read/page-000.xml+2-2| ... | @@ -1,4 +1,4 @@ | ... | @@ -1,4 +1,4 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{D3A0AD9C-D12D-02B2-1683-4D32345076A5}{1}{B0}" name="Before the picture" dateTime="2026-09-12T20:07:15.000Z" lastModifiedTime="2026-09-12T20:08:56.000Z" pageLevel="1" lang="en-US"><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-12T20:07:15.000Z" objectID="{54DE5097-F4BE-0BCC-0392-19FB4210AAAD}{23}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="148.3100738525391" height="27.60543251037598"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-12T20:07:15.000Z" lastModifiedTime="2026-09-12T20:07:15.000Z" objectID="{54DE5097-F4BE-0BCC-0392-19FB4210AAAD}{24}{B0}" alignment="left" style="font-family:Calibri;font-size:11.0pt"><one:T><![CDATA[Before the picture]]></one:T></one:OE><one:OE author="Picture author" lastModifiedBy="Picture author" creationTime="2026-09-12T20:07:15.000Z" lastModifiedTime="2026-09-12T20:07:15.000Z" objectID="{496D54CF-B74C-091A-2D00-F268C53481D1}{0}{B0}" alignment="left"><one:Image format="png"><one:Data>iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVQI12P4z8AAAAMBAQAY3Y2w | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{A3568077-FCA2-0E53-0B6A-615C7F205498}{1}{B0}" name="Before the picture" dateTime="2026-09-26T06:32:50.000Z" lastModifiedTime="2026-09-26T06:43:15.000Z" pageLevel="1" lang="en-US"><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-26T06:32:50.000Z" objectID="{A1F8FA80-B0EC-0254-0FC4-F867B3DD72E7}{23}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="148.3100738525391" height="27.60543251037598"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-26T06:32:50.000Z" lastModifiedTime="2026-09-26T06:32:50.000Z" objectID="{A1F8FA80-B0EC-0254-0FC4-F867B3DD72E7}{24}{B0}" alignment="left" style="font-family:Calibri;font-size:11.0pt"><one:T><![CDATA[Before the picture]]></one:T></one:OE><one:OE author="Picture author" lastModifiedBy="Picture author" creationTime="2026-09-26T06:32:50.000Z" lastModifiedTime="2026-09-26T06:32:50.000Z" objectID="{21F2D72A-8493-0751-389D-FA29710E456D}{1}{B0}" alignment="left"><one:Image format="png"><one:Data>iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVQI12P4z8AAAAMBAQAY3Y2w |
| 3 | AAAAAElFTkSuQmCC | 3 | AAAAAElFTkSuQmCC |
| 4 | </one:Data></one:Image></one:OE><one:OE author="Picture author" lastModifiedBy="Picture author" creationTime="2026-09-12T20:07:15.000Z" lastModifiedTime="2026-09-12T20:07:15.000Z" objectID="{39F25FF2-3F08-07F1-06E1-F7F0075DE8F1}{0}{B0}" alignment="left" style="font-family:Calibri;font-size:11.0pt"><one:T><![CDATA[After the picture]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> | 4 | </one:Data></one:Image></one:OE><one:OE author="Picture author" lastModifiedBy="Picture author" creationTime="2026-09-26T06:32:50.000Z" lastModifiedTime="2026-09-26T06:32:50.000Z" objectID="{21F2D72A-8493-0751-389D-FA29710E456D}{4}{B0}" alignment="left" style="font-family:Calibri;font-size:11.0pt"><one:T><![CDATA[After the picture]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/picture-edit/inserted/cold/run.json+1-1| ... | @@ -1,5 +1,5 @@ | ... | @@ -1,5 +1,5 @@ |
| 1 | { | 1 | { |
| 2 | "notebook": "/private/tmp/m4/pictures", | 2 | "notebook": "/Users/clo/dev/one/evidence/titles/picture-inserted", |
| 3 | "expected_pages": 1, | 3 | "expected_pages": 1, |
| 4 | "author": null, | 4 | "author": null, |
| 5 | "author_timeout_seconds": 600, | 5 | "author_timeout_seconds": 600, |
corpus/picture-edit/inserted/cold/source.json+5-5| ... | @@ -2,13 +2,13 @@ | ... | @@ -2,13 +2,13 @@ |
| 2 | { | 2 | { |
| 3 | "path": "Open Notebook.onetoc2", | 3 | "path": "Open Notebook.onetoc2", |
| 4 | "bytes": 1604, | 4 | "bytes": 1604, |
| 5 | "sha256": "0eddd691b52d26bac222cf7e52add3c9909446647b452fb193f03d49e273426a", | 5 | "sha256": "52e4f16e4fc2b25971d0b1e2504679373b413d65a5127833f874335115ace5c9", |
| 6 | "mtime_ns": 1789243635388246438 | 6 | "mtime_ns": 1790404370185679445 |
| 7 | }, | 7 | }, |
| 8 | { | 8 | { |
| 9 | "path": "pictures.one", | 9 | "path": "pictures.one", |
| 10 | "bytes": 7064, | 10 | "bytes": 7776, |
| 11 | "sha256": "3076880e02cf74eb85400852c133060ab8ed50383c6b9df8a07bde8389891a70", | 11 | "sha256": "c468fdcd8d55b5161c9e8f104d846c4191c40efb4bdc9a6aa134f8d824627436", |
| 12 | "mtime_ns": 1789243635387879438 | 12 | "mtime_ns": 1790404370185326028 |
| 13 | } | 13 | } |
| 14 | ] | 14 | ] |
corpus/picture-edit/page-level/candidate/Open Notebook.onetoc2| Binary files a/corpus/picture-edit/page-level/candidate/Open Notebook.onetoc2 and b/corpus/picture-edit/page-level/candidate/Open Notebook.onetoc2 differ | |||
corpus/picture-edit/page-level/candidate/pictures.one| Binary files a/corpus/picture-edit/page-level/candidate/pictures.one and b/corpus/picture-edit/page-level/candidate/pictures.one differ | |||
corpus/picture-edit/page-level/cold/commands.jsonl+1-1| ... | @@ -1,3 +1,3 @@ | ... | @@ -1,3 +1,3 @@ |
| 1 | {"command": "powershell -NoProfile -Command \"Expand-Archive -LiteralPath C:\\one-tests\\transfer.zip -DestinationPath C:\\one-tests\\runs\\capture\\notebook\"", "exit": 0, "stdout": "", "stderr": "", "error": null} | 1 | {"command": "powershell -NoProfile -Command \"Expand-Archive -LiteralPath C:\\one-tests\\transfer.zip -DestinationPath C:\\one-tests\\runs\\capture\\notebook\"", "exit": 0, "stdout": "", "stderr": "", "error": null} |
| 2 | {"command": "powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File C:\\one-tests\\read-current.ps1 -Root C:\\one-tests\\runs\\capture -CloneHost ONE-M6-3E57E5E3 -ExpectedPages 1", "exit": 0, "stdout": "Read 1 sections and 1 pages.\r\n", "stderr": "", "error": null} | 2 | {"command": "powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File C:\\one-tests\\read-current.ps1 -Root C:\\one-tests\\runs\\capture -CloneHost ONE-M6-F9B47642 -ExpectedPages 1", "exit": 0, "stdout": "Read 1 sections and 1 pages.\r\n", "stderr": "", "error": null} |
| 3 | {"command": "powershell -NoProfile -Command \"Compress-Archive -Force -Path C:\\one-tests\\runs\\capture\\* -DestinationPath C:\\one-tests\\captured.zip\"", "exit": 0, "stdout": "", "stderr": "", "error": null} | 3 | {"command": "powershell -NoProfile -Command \"Compress-Archive -Force -Path C:\\one-tests\\runs\\capture\\* -DestinationPath C:\\one-tests\\captured.zip\"", "exit": 0, "stdout": "", "stderr": "", "error": null} |
corpus/picture-edit/page-level/cold/machine.json+1-1| ... | @@ -1 +1 @@ | ... | @@ -1 +1 @@ |
| 1 | {"name": "m6-3e57e5e3", "hostname": "ONE-M6-3E57E5E3"} | 1 | {"name": "m6-f9b47642", "hostname": "ONE-M6-F9B47642"} |
corpus/picture-edit/page-level/cold/notebook/Open Notebook.onetoc2| Binary files a/corpus/picture-edit/page-level/cold/notebook/Open Notebook.onetoc2 and b/corpus/picture-edit/page-level/cold/notebook/Open Notebook.onetoc2 differ | |||
corpus/picture-edit/page-level/cold/notebook/pictures.one| Binary files a/corpus/picture-edit/page-level/cold/notebook/pictures.one and b/corpus/picture-edit/page-level/cold/notebook/pictures.one differ | |||
corpus/picture-edit/page-level/cold/read/environment.json+1-1| ... | @@ -1,7 +1,7 @@ | ... | @@ -1,7 +1,7 @@ |
| 1 | { | 1 | { |
| 2 | "powershell": "5.1.14409.1005", | 2 | "powershell": "5.1.14409.1005", |
| 3 | "schema": "xs2010", | 3 | "schema": "xs2010", |
| 4 | "hostname": "ONE-M6-3E57E5E3", | 4 | "hostname": "ONE-M6-F9B47642", |
| 5 | "cold": true, | 5 | "cold": true, |
| 6 | "onenote": "14.0.4763.1000" | 6 | "onenote": "14.0.4763.1000" |
| 7 | } | 7 | } |
corpus/picture-edit/page-level/cold/read/hierarchy.xml+1-1| ... | @@ -1,2 +1,2 @@ | ... | @@ -1,2 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Notebook xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" name="notebook" nickname="notebook" ID="{7979B79B-55CA-4BD2-9516-7F2833A0B2D3}{1}{B0}" path="C:\one-tests\runs\capture\notebook" lastModifiedTime="2026-09-12T21:40:13.000Z" color="#B49EDE"><one:Section name="pictures" ID="{1037AF3E-61F8-0E78-3745-6713BE53BE60}{1}{B0}" path="C:\one-tests\runs\capture\notebook\pictures.one" lastModifiedTime="2026-09-12T21:40:13.000Z" color="#8AA8E4"><one:Page ID="{8EDE3CB0-E79B-0683-237C-820507E0B6A6}{1}{B0}" name="Beside the picture" dateTime="2026-09-12T21:38:24.000Z" lastModifiedTime="2026-09-12T21:40:13.000Z" pageLevel="1"/></one:Section></one:Notebook> | 2 | <one:Notebook xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" name="notebook" nickname="notebook" ID="{B542D228-D5B7-427D-8774-02257F272F0D}{1}{B0}" path="C:\one-tests\runs\capture\notebook" lastModifiedTime="2026-09-26T06:46:48.000Z" color="#B49EDE"><one:Section name="pictures" ID="{54923A9D-64F0-0307-13E1-F2E6E667FEEE}{1}{B0}" path="C:\one-tests\runs\capture\notebook\pictures.one" lastModifiedTime="2026-09-26T06:46:48.000Z" color="#8AA8E4"><one:Page ID="{7D08EA18-6182-09A0-304C-6E0D82ECE15F}{1}{B0}" name="Beside the picture" dateTime="2026-09-26T06:32:50.000Z" lastModifiedTime="2026-09-26T06:46:39.000Z" pageLevel="1"/></one:Section></one:Notebook> |
corpus/picture-edit/page-level/cold/read/page-000.xml+1-1| ... | @@ -1,4 +1,4 @@ | ... | @@ -1,4 +1,4 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{8EDE3CB0-E79B-0683-237C-820507E0B6A6}{1}{B0}" name="Beside the picture" dateTime="2026-09-12T21:38:24.000Z" lastModifiedTime="2026-09-12T21:40:13.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-12T21:38:24.000Z" objectID="{1037AF3E-61F8-0E78-3745-6713BE53BE60}{23}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="147.9233703613281" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-12T21:38:24.000Z" lastModifiedTime="2026-09-12T21:38:24.000Z" objectID="{1037AF3E-61F8-0E78-3745-6713BE53BE60}{24}{B0}" alignment="left"><one:T><![CDATA[Beside the picture]]></one:T></one:OE></one:OEChildren></one:Outline><one:Image format="png" alt="Placed in Rust" lastModifiedTime="2026-09-12T21:38:24.000Z" objectID="{03E09DC9-97C4-05DC-32C4-49615A981E9C}{0}{B0}"><one:Position x="360.0" y="240.0" z="1"/><one:Size width="96.00000762939453" height="71.99998474121093" isSetByUser="true"/><one:Data>iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVQI12P4z8AAAAMBAQAY3Y2w | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{7D08EA18-6182-09A0-304C-6E0D82ECE15F}{1}{B0}" name="Beside the picture" dateTime="2026-09-26T06:32:50.000Z" lastModifiedTime="2026-09-26T06:46:39.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-26T06:32:50.000Z" objectID="{54923A9D-64F0-0307-13E1-F2E6E667FEEE}{23}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="147.9233703613281" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-26T06:32:50.000Z" lastModifiedTime="2026-09-26T06:32:50.000Z" objectID="{54923A9D-64F0-0307-13E1-F2E6E667FEEE}{24}{B0}" alignment="left"><one:T><![CDATA[Beside the picture]]></one:T></one:OE></one:OEChildren></one:Outline><one:Image format="png" alt="Placed in Rust" lastModifiedTime="2026-09-26T06:32:50.000Z" objectID="{D1E701D5-AF6D-0088-33CF-6A1696900977}{1}{B0}"><one:Position x="360.0" y="240.0" z="1"/><one:Size width="96.00000762939453" height="71.99998474121093" isSetByUser="true"/><one:Data>iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVQI12P4z8AAAAMBAQAY3Y2w |
| 3 | AAAAAElFTkSuQmCC | 3 | AAAAAElFTkSuQmCC |
| 4 | </one:Data></one:Image></one:Page> | 4 | </one:Data></one:Image></one:Page> |
corpus/picture-edit/page-level/cold/run.json+1-1| ... | @@ -1,5 +1,5 @@ | ... | @@ -1,5 +1,5 @@ |
| 1 | { | 1 | { |
| 2 | "notebook": "/private/tmp/m4/pagelevel", | 2 | "notebook": "/Users/clo/dev/one/evidence/titles/picture-page-level", |
| 3 | "expected_pages": 1, | 3 | "expected_pages": 1, |
| 4 | "author": null, | 4 | "author": null, |
| 5 | "author_timeout_seconds": 600, | 5 | "author_timeout_seconds": 600, |
corpus/picture-edit/page-level/cold/source.json+5-5| ... | @@ -2,13 +2,13 @@ | ... | @@ -2,13 +2,13 @@ |
| 2 | { | 2 | { |
| 3 | "path": "Open Notebook.onetoc2", | 3 | "path": "Open Notebook.onetoc2", |
| 4 | "bytes": 1604, | 4 | "bytes": 1604, |
| 5 | "sha256": "3df6c788afbfe6ecda97d63cdbb3f42b269d61107e78dd0b3babaf55ae567389", | 5 | "sha256": "8d7fed8f27dfd76533e619068e37c1ee0de5187faebb129c5047c01a814f4c9f", |
| 6 | "mtime_ns": 1789249104766253246 | 6 | "mtime_ns": 1790404370294442700 |
| 7 | }, | 7 | }, |
| 8 | { | 8 | { |
| 9 | "path": "pictures.one", | 9 | "path": "pictures.one", |
| 10 | "bytes": 5656, | 10 | "bytes": 6560, |
| 11 | "sha256": "b6c0a2e604c038a2d0fa93a6096c3fecfa08f370f268ad010d7419ee87dd1f02", | 11 | "sha256": "80dd6cc455bbbffe37d9cb7ad30d69a4a6a65b550a5de3d87a514bc0f99c5f41", |
| 12 | "mtime_ns": 1789249104766006120 | 12 | "mtime_ns": 1790404370294185825 |
| 13 | } | 13 | } |
| 14 | ] | 14 | ] |
corpus/table-edit/README.md+2-2| ... | @@ -23,8 +23,8 @@ two-row table with locked 72 pt column and visible borders. `nested/cold` is | ... | @@ -23,8 +23,8 @@ two-row table with locked 72 pt column and visible borders. `nested/cold` is |
| 23 | its cold read with both tables. | 23 | its cold read with both tables. |
| 24 | 24 | ||
| 25 | Native rows and cells are plain containers; the writer creates them with the | 25 | Native rows and cells are plain containers; the writer creates them with the |
| 26 | modification time, the indent array copied from a sibling cell, and the flags | 26 | modification time, the indent array copied from a sibling cell (OneNote's |
| 27 | every native cell carries, rebuilds each table's row and cell order to the | 27 | default without one), and the flags every native cell carries, rebuilds each table's row and cell order to the |
| 28 | model's, and writes the column count, widths, locks and border flag. New | 28 | model's, and writes the column count, widths, locks and border flag. New |
| 29 | tables start as an empty paragraph whose content the structure pass replaces. | 29 | tables start as an empty paragraph whose content the structure pass replaces. |
| 30 | An emptied cell keeps a replacement paragraph, as the tree writer provides. | 30 | An emptied cell keeps a replacement paragraph, as the tree writer provides. |
corpus/table-edit/created/candidate/Open Notebook.onetoc2| Binary files a/corpus/table-edit/created/candidate/Open Notebook.onetoc2 and b/corpus/table-edit/created/candidate/Open Notebook.onetoc2 differ | |||
corpus/table-edit/created/candidate/tables.one| Binary files a/corpus/table-edit/created/candidate/tables.one and b/corpus/table-edit/created/candidate/tables.one differ | |||
corpus/table-edit/created/cold/commands.jsonl+1-1| ... | @@ -1,3 +1,3 @@ | ... | @@ -1,3 +1,3 @@ |
| 1 | {"command": "powershell -NoProfile -Command \"Expand-Archive -LiteralPath C:\\one-tests\\transfer.zip -DestinationPath C:\\one-tests\\runs\\capture\\notebook\"", "exit": 0, "stdout": "", "stderr": "", "error": null} | 1 | {"command": "powershell -NoProfile -Command \"Expand-Archive -LiteralPath C:\\one-tests\\transfer.zip -DestinationPath C:\\one-tests\\runs\\capture\\notebook\"", "exit": 0, "stdout": "", "stderr": "", "error": null} |
| 2 | {"command": "powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File C:\\one-tests\\read-current.ps1 -Root C:\\one-tests\\runs\\capture -CloneHost ONE-M6-838F23A9 -ExpectedPages 1", "exit": 0, "stdout": "Read 1 sections and 1 pages.\r\n", "stderr": "", "error": null} | 2 | {"command": "powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File C:\\one-tests\\read-current.ps1 -Root C:\\one-tests\\runs\\capture -CloneHost ONE-M6-D27A9E1E -ExpectedPages 1", "exit": 0, "stdout": "Read 1 sections and 1 pages.\r\n", "stderr": "", "error": null} |
| 3 | {"command": "powershell -NoProfile -Command \"Compress-Archive -Force -Path C:\\one-tests\\runs\\capture\\* -DestinationPath C:\\one-tests\\captured.zip\"", "exit": 0, "stdout": "", "stderr": "", "error": null} | 3 | {"command": "powershell -NoProfile -Command \"Compress-Archive -Force -Path C:\\one-tests\\runs\\capture\\* -DestinationPath C:\\one-tests\\captured.zip\"", "exit": 0, "stdout": "", "stderr": "", "error": null} |
corpus/table-edit/created/cold/machine.json+1-1| ... | @@ -1 +1 @@ | ... | @@ -1 +1 @@ |
| 1 | {"name": "m6-838f23a9", "hostname": "ONE-M6-838F23A9"} | 1 | {"name": "m6-d27a9e1e", "hostname": "ONE-M6-D27A9E1E"} |
corpus/table-edit/created/cold/notebook/Open Notebook.onetoc2| Binary files a/corpus/table-edit/created/cold/notebook/Open Notebook.onetoc2 and b/corpus/table-edit/created/cold/notebook/Open Notebook.onetoc2 differ | |||
corpus/table-edit/created/cold/notebook/tables.one| Binary files a/corpus/table-edit/created/cold/notebook/tables.one and b/corpus/table-edit/created/cold/notebook/tables.one differ | |||
corpus/table-edit/created/cold/read/environment.json+1-1| ... | @@ -1,7 +1,7 @@ | ... | @@ -1,7 +1,7 @@ |
| 1 | { | 1 | { |
| 2 | "powershell": "5.1.14409.1005", | 2 | "powershell": "5.1.14409.1005", |
| 3 | "schema": "xs2010", | 3 | "schema": "xs2010", |
| 4 | "hostname": "ONE-M6-838F23A9", | 4 | "hostname": "ONE-M6-D27A9E1E", |
| 5 | "cold": true, | 5 | "cold": true, |
| 6 | "onenote": "14.0.4763.1000" | 6 | "onenote": "14.0.4763.1000" |
| 7 | } | 7 | } |
corpus/table-edit/created/cold/read/hierarchy.xml+1-1| ... | @@ -1,2 +1,2 @@ | ... | @@ -1,2 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Notebook xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" name="notebook" nickname="notebook" ID="{1BB56A3F-9C37-4838-B671-213C7548F681}{1}{B0}" path="C:\one-tests\runs\capture\notebook" lastModifiedTime="2026-09-12T18:57:57.000Z" color="#B49EDE"><one:Section name="tables" ID="{1C2D662F-7BFD-0276-1BFA-B3E7E7E01562}{1}{B0}" path="C:\one-tests\runs\capture\notebook\tables.one" lastModifiedTime="2026-09-12T18:57:57.000Z" color="#8AA8E4"><one:Page ID="{1A74DE87-A9CF-0A2A-057E-CDD4A592DF04}{1}{B0}" name="Before the table" dateTime="2026-09-12T18:55:42.000Z" lastModifiedTime="2026-09-12T18:57:46.000Z" pageLevel="1"/></one:Section></one:Notebook> | 2 | <one:Notebook xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" name="notebook" nickname="notebook" ID="{391C6ED8-573A-4D96-A456-F04ECBADE242}{1}{B0}" path="C:\one-tests\runs\capture\notebook" lastModifiedTime="2026-09-26T06:43:15.000Z" color="#B49EDE"><one:Section name="tables" ID="{04A30DC8-E0D5-038E-0AC2-29BF67C23E5C}{1}{B0}" path="C:\one-tests\runs\capture\notebook\tables.one" lastModifiedTime="2026-09-26T06:43:15.000Z" color="#8AA8E4"><one:Page ID="{FAB80BEA-DF75-0534-21D7-A812FF413639}{1}{B0}" name="Before the table" dateTime="2026-09-26T06:32:49.000Z" lastModifiedTime="2026-09-26T06:43:15.000Z" pageLevel="1"/></one:Section></one:Notebook> |
corpus/table-edit/created/cold/read/page-000.xml+1-1| ... | @@ -1,2 +1,2 @@ | ... | @@ -1,2 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{1A74DE87-A9CF-0A2A-057E-CDD4A592DF04}{1}{B0}" name="Before the table" dateTime="2026-09-12T18:55:42.000Z" lastModifiedTime="2026-09-12T18:57:46.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-12T18:55:42.000Z" objectID="{1C2D662F-7BFD-0276-1BFA-B3E7E7E01562}{23}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="395.1299438476562" height="67.21086883544922"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-12T18:55:42.000Z" lastModifiedTime="2026-09-12T18:55:42.000Z" objectID="{1C2D662F-7BFD-0276-1BFA-B3E7E7E01562}{24}{B0}" alignment="left"><one:T><![CDATA[Before the table]]></one:T></one:OE><one:OE author="Table author" lastModifiedBy="Table author" creationTime="2026-09-12T18:55:42.000Z" lastModifiedTime="2026-09-12T18:55:42.000Z" objectID="{3A30E194-5242-09E5-2837-A8BE31520023}{0}{B0}" alignment="left"><one:Table bordersVisible="true" lastModifiedTime="2026-09-12T18:55:42.000Z" objectID="{2BAC850E-BEEF-0DFE-101D-E7A08FA3EF36}{0}{B0}"><one:Columns><one:Column index="0" width="144.0" isLocked="true"/><one:Column index="1" width="216.0" isLocked="true"/></one:Columns><one:Row objectID="{32B84587-8B60-0900-2C7D-8B03AA1021FC}{0}{B0}" lastModifiedTime="2026-09-12T18:55:42.000Z"><one:Cell lastModifiedTime="2026-09-12T18:55:42.000Z" objectID="{35FB065B-82B3-06B6-23B5-4A866D422733}{0}{B0}"><one:OEChildren><one:OE creationTime="2026-09-12T18:55:42.000Z" lastModifiedTime="2026-09-12T18:55:42.000Z" objectID="{6961CA5C-5601-0109-3ABA-9C6E1D87807B}{0}{B0}" alignment="left"><one:T><![CDATA[Name]]></one:T></one:OE></one:OEChildren></one:Cell><one:Cell lastModifiedTime="2026-09-12T18:55:42.000Z" objectID="{6460510E-A7E1-04D3-21FE-7B255A488496}{0}{B0}"><one:OEChildren><one:OE creationTime="2026-09-12T18:55:42.000Z" lastModifiedTime="2026-09-12T18:55:42.000Z" objectID="{251F55AF-E7E8-0E35-3FFF-A0188DB1E006}{0}{B0}" alignment="left"><one:T><![CDATA[Value]]></one:T></one:OE></one:OEChildren></one:Cell></one:Row><one:Row objectID="{E5A88DF0-4872-0A5B-03CE-6AD2D88CC6A2}{0}{B0}" lastModifiedTime="2026-09-12T18:55:42.000Z"><one:Cell lastModifiedTime="2026-09-12T18:55:42.000Z" objectID="{65FC0F6F-A7F8-01D8-2F86-E81451D010FB}{0}{B0}"><one:OEChildren><one:OE creationTime="2026-09-12T18:55:42.000Z" lastModifiedTime="2026-09-12T18:55:42.000Z" objectID="{1F1346CC-AB72-0AC2-0F02-C82B60ACE63B}{0}{B0}" alignment="left"><one:T><![CDATA[Rust &#129408;]]></one:T></one:OE></one:OEChildren></one:Cell><one:Cell lastModifiedTime="2026-09-12T18:55:42.000Z" objectID="{4A655FAB-04B2-0641-3895-C87F7F551A21}{0}{B0}"><one:OEChildren><one:OE creationTime="2026-09-12T18:55:42.000Z" lastModifiedTime="2026-09-12T18:55:42.000Z" objectID="{5BBFE54E-D6A1-0A3C-0302-2F0ED58341F1}{0}{B0}" alignment="left"><one:T><![CDATA[Written natively]]></one:T></one:OE></one:OEChildren></one:Cell></one:Row></one:Table></one:OE><one:OE author="Table author" lastModifiedBy="Table author" creationTime="2026-09-12T18:55:42.000Z" lastModifiedTime="2026-09-12T18:55:42.000Z" objectID="{A750F1FF-86E2-0E24-2656-CFDC574202E9}{0}{B0}" alignment="left"><one:T><![CDATA[After the table]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{FAB80BEA-DF75-0534-21D7-A812FF413639}{1}{B0}" name="Before the table" dateTime="2026-09-26T06:32:49.000Z" lastModifiedTime="2026-09-26T06:43:15.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-26T06:32:49.000Z" objectID="{04A30DC8-E0D5-038E-0AC2-29BF67C23E5C}{23}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="395.1299438476562" height="67.21086883544922"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-26T06:32:49.000Z" lastModifiedTime="2026-09-26T06:32:49.000Z" objectID="{04A30DC8-E0D5-038E-0AC2-29BF67C23E5C}{24}{B0}" alignment="left"><one:T><![CDATA[Before the table]]></one:T></one:OE><one:OE author="Table author" lastModifiedBy="Table author" creationTime="2026-09-26T06:32:49.000Z" lastModifiedTime="2026-09-26T06:32:49.000Z" objectID="{E8153D58-8C73-0F63-0636-2ACA309F04F1}{16}{B0}" alignment="left"><one:Table bordersVisible="true" lastModifiedTime="2026-09-26T06:32:49.000Z" objectID="{E8153D58-8C73-0F63-0636-2ACA309F04F1}{1}{B0}"><one:Columns><one:Column index="0" width="144.0" isLocked="true"/><one:Column index="1" width="216.0" isLocked="true"/></one:Columns><one:Row objectID="{E8153D58-8C73-0F63-0636-2ACA309F04F1}{2}{B0}" lastModifiedTime="2026-09-26T06:32:49.000Z"><one:Cell lastModifiedTime="2026-09-26T06:32:49.000Z" objectID="{E8153D58-8C73-0F63-0636-2ACA309F04F1}{3}{B0}"><one:OEChildren><one:OE creationTime="2026-09-26T06:32:49.000Z" lastModifiedTime="2026-09-26T06:32:49.000Z" objectID="{E8153D58-8C73-0F63-0636-2ACA309F04F1}{4}{B0}" alignment="left"><one:T><![CDATA[Name]]></one:T></one:OE></one:OEChildren></one:Cell><one:Cell lastModifiedTime="2026-09-26T06:32:49.000Z" objectID="{E8153D58-8C73-0F63-0636-2ACA309F04F1}{6}{B0}"><one:OEChildren><one:OE creationTime="2026-09-26T06:32:49.000Z" lastModifiedTime="2026-09-26T06:32:49.000Z" objectID="{E8153D58-8C73-0F63-0636-2ACA309F04F1}{7}{B0}" alignment="left"><one:T><![CDATA[Value]]></one:T></one:OE></one:OEChildren></one:Cell></one:Row><one:Row objectID="{E8153D58-8C73-0F63-0636-2ACA309F04F1}{9}{B0}" lastModifiedTime="2026-09-26T06:32:49.000Z"><one:Cell lastModifiedTime="2026-09-26T06:32:49.000Z" objectID="{E8153D58-8C73-0F63-0636-2ACA309F04F1}{10}{B0}"><one:OEChildren><one:OE creationTime="2026-09-26T06:32:49.000Z" lastModifiedTime="2026-09-26T06:32:49.000Z" objectID="{E8153D58-8C73-0F63-0636-2ACA309F04F1}{11}{B0}" alignment="left"><one:T><![CDATA[Rust &#129408;]]></one:T></one:OE></one:OEChildren></one:Cell><one:Cell lastModifiedTime="2026-09-26T06:32:49.000Z" objectID="{E8153D58-8C73-0F63-0636-2ACA309F04F1}{13}{B0}"><one:OEChildren><one:OE creationTime="2026-09-26T06:32:49.000Z" lastModifiedTime="2026-09-26T06:32:49.000Z" objectID="{E8153D58-8C73-0F63-0636-2ACA309F04F1}{14}{B0}" alignment="left"><one:T><![CDATA[Written natively]]></one:T></one:OE></one:OEChildren></one:Cell></one:Row></one:Table></one:OE><one:OE author="Table author" lastModifiedBy="Table author" creationTime="2026-09-26T06:32:49.000Z" lastModifiedTime="2026-09-26T06:32:49.000Z" objectID="{E8153D58-8C73-0F63-0636-2ACA309F04F1}{18}{B0}" alignment="left"><one:T><![CDATA[After the table]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/table-edit/created/cold/run.json+1-1| ... | @@ -1,5 +1,5 @@ | ... | @@ -1,5 +1,5 @@ |
| 1 | { | 1 | { |
| 2 | "notebook": "/private/tmp/m4/tables", | 2 | "notebook": "/Users/clo/dev/one/evidence/titles/table-created", |
| 3 | "expected_pages": 1, | 3 | "expected_pages": 1, |
| 4 | "author": null, | 4 | "author": null, |
| 5 | "author_timeout_seconds": 600, | 5 | "author_timeout_seconds": 600, |
corpus/table-edit/created/cold/source.json+5-5| ... | @@ -2,13 +2,13 @@ | ... | @@ -2,13 +2,13 @@ |
| 2 | { | 2 | { |
| 3 | "path": "Open Notebook.onetoc2", | 3 | "path": "Open Notebook.onetoc2", |
| 4 | "bytes": 1604, | 4 | "bytes": 1604, |
| 5 | "sha256": "b39b2e142e0adce0de37ad336f40c3e9c2ba92ce6927c9d109139e74c1fb4a6e", | 5 | "sha256": "8e64e22a5381e45d42536c1d75cf266a70970828d0258c15571adc6f4d75391f", |
| 6 | "mtime_ns": 1789239342755220465 | 6 | "mtime_ns": 1790404369445796875 |
| 7 | }, | 7 | }, |
| 8 | { | 8 | { |
| 9 | "path": "tables.one", | 9 | "path": "tables.one", |
| 10 | "bytes": 11016, | 10 | "bytes": 10936, |
| 11 | "sha256": "e1c014220e05352a01c98752031b32c17512407295aa812ff8e9c776e48eef6e", | 11 | "sha256": "d66db22225eaf0064e53f14e6dcfdbefd822c292d839be6296bc442153c278e3", |
| 12 | "mtime_ns": 1789239342754903465 | 12 | "mtime_ns": 1790404369445435251 |
| 13 | } | 13 | } |
| 14 | ] | 14 | ] |
corpus/table-edit/nested/candidate/Open Notebook.onetoc2| Binary files a/corpus/table-edit/nested/candidate/Open Notebook.onetoc2 and b/corpus/table-edit/nested/candidate/Open Notebook.onetoc2 differ | |||
corpus/table-edit/nested/candidate/synthetic.one| Binary files a/corpus/table-edit/nested/candidate/synthetic.one and b/corpus/table-edit/nested/candidate/synthetic.one differ | |||
corpus/table-edit/nested/cold/commands.jsonl+1-1| ... | @@ -1,3 +1,3 @@ | ... | @@ -1,3 +1,3 @@ |
| 1 | {"command": "powershell -NoProfile -Command \"Expand-Archive -LiteralPath C:\\one-tests\\transfer.zip -DestinationPath C:\\one-tests\\runs\\capture\\notebook\"", "exit": 0, "stdout": "", "stderr": "", "error": null} | 1 | {"command": "powershell -NoProfile -Command \"Expand-Archive -LiteralPath C:\\one-tests\\transfer.zip -DestinationPath C:\\one-tests\\runs\\capture\\notebook\"", "exit": 0, "stdout": "", "stderr": "", "error": null} |
| 2 | {"command": "powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File C:\\one-tests\\read-current.ps1 -Root C:\\one-tests\\runs\\capture -CloneHost ONE-M6-8E63A852 -ExpectedPages 12", "exit": 0, "stdout": "Read 1 sections and 12 pages.\r\n", "stderr": "", "error": null} | 2 | {"command": "powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File C:\\one-tests\\read-current.ps1 -Root C:\\one-tests\\runs\\capture -CloneHost ONE-M6-B7496B80 -ExpectedPages 12", "exit": 0, "stdout": "Read 1 sections and 12 pages.\r\n", "stderr": "", "error": null} |
| 3 | {"command": "powershell -NoProfile -Command \"Compress-Archive -Force -Path C:\\one-tests\\runs\\capture\\* -DestinationPath C:\\one-tests\\captured.zip\"", "exit": 0, "stdout": "", "stderr": "", "error": null} | 3 | {"command": "powershell -NoProfile -Command \"Compress-Archive -Force -Path C:\\one-tests\\runs\\capture\\* -DestinationPath C:\\one-tests\\captured.zip\"", "exit": 0, "stdout": "", "stderr": "", "error": null} |
corpus/table-edit/nested/cold/machine.json+1-1| ... | @@ -1 +1 @@ | ... | @@ -1 +1 @@ |
| 1 | {"name": "m6-8e63a852", "hostname": "ONE-M6-8E63A852"} | 1 | {"name": "m6-b7496b80", "hostname": "ONE-M6-B7496B80"} |
corpus/table-edit/nested/cold/notebook/Open Notebook.onetoc2| Binary files a/corpus/table-edit/nested/cold/notebook/Open Notebook.onetoc2 and b/corpus/table-edit/nested/cold/notebook/Open Notebook.onetoc2 differ | |||
corpus/table-edit/nested/cold/notebook/synthetic.one| Binary files a/corpus/table-edit/nested/cold/notebook/synthetic.one and b/corpus/table-edit/nested/cold/notebook/synthetic.one differ | |||
corpus/table-edit/nested/cold/read/environment.json+1-1| ... | @@ -1,7 +1,7 @@ | ... | @@ -1,7 +1,7 @@ |
| 1 | { | 1 | { |
| 2 | "powershell": "5.1.14409.1005", | 2 | "powershell": "5.1.14409.1005", |
| 3 | "schema": "xs2010", | 3 | "schema": "xs2010", |
| 4 | "hostname": "ONE-M6-8E63A852", | 4 | "hostname": "ONE-M6-B7496B80", |
| 5 | "cold": true, | 5 | "cold": true, |
| 6 | "onenote": "14.0.4763.1000" | 6 | "onenote": "14.0.4763.1000" |
| 7 | } | 7 | } |
corpus/table-edit/nested/cold/read/hierarchy.xml+1-1| ... | @@ -1,2 +1,2 @@ | ... | @@ -1,2 +1,2 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Notebook xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" name="notebook" nickname="notebook" ID="{D5C61ACD-0C18-47AB-9BB7-EA0453092F1A}{1}{B0}" path="C:\one-tests\runs\capture\notebook" lastModifiedTime="2026-09-12T21:55:30.000Z" color="#B49EDE"><one:Section name="synthetic" ID="{DCA72CDB-70D0-04AA-3F56-92B886A9FA01}{1}{B0}" path="C:\one-tests\runs\capture\notebook\synthetic.one" lastModifiedTime="2026-09-12T21:55:30.000Z" color="#8AA8E4"><one:Page ID="{F0EEDF5D-F91B-47BB-B831-3A297A91E61E}{1}{B0}" name="Bold 🦀 italic é color 東京" dateTime="2026-09-05T05:06:54.000Z" lastModifiedTime="2026-09-12T21:55:19.000Z" pageLevel="1"/><one:Page ID="{C131A2C2-2BC5-4502-8CFF-F5FCAC5673F9}{1}{B0}" name="Indent first subtree" dateTime="2026-09-08T19:25:53.000Z" lastModifiedTime="2026-09-12T21:55:19.000Z" pageLevel="1"/><one:Page ID="{80C90902-0F49-4C7B-8E7F-596216EE4379}{1}{B0}" name="Outdent first group" dateTime="2026-09-08T19:25:55.000Z" lastModifiedTime="2026-09-12T21:55:19.000Z" pageLevel="1"/><one:Page ID="{B78DF4E7-9F1A-454E-BE5B-F35A57E182C9}{1}{B0}" name="Delete only grouped subtree" dateTime="2026-09-08T19:25:55.000Z" lastModifiedTime="2026-09-12T21:55:19.000Z" pageLevel="1"/><one:Page ID="{F0F484B0-2100-4784-B9E5-4A8B81EF98E2}{1}{B0}" name="Delete unindented sibling after group" dateTime="2026-09-08T19:25:55.000Z" lastModifiedTime="2026-09-12T21:55:19.000Z" pageLevel="1"/><one:Page ID="{56E30880-7E3F-485A-BAC8-09FC9BA6B44B}{1}{B0}" name="Delete cell subtree" dateTime="2026-09-08T19:25:56.000Z" lastModifiedTime="2026-09-12T21:55:19.000Z" pageLevel="1"/><one:Page ID="{D1132B3D-E3A6-4819-8341-86F33DC7D2E2}{1}{B0}" name="Delete sole cell paragraph" dateTime="2026-09-08T19:25:56.000Z" lastModifiedTime="2026-09-12T21:55:19.000Z" pageLevel="1"/><one:Page ID="{676EECC9-3E35-4A9E-AF43-A9882BDDAC59}{1}{B0}" name="Move numbered subtree down" dateTime="2026-09-08T19:25:57.000Z" lastModifiedTime="2026-09-12T21:55:19.000Z" pageLevel="1"/><one:Page ID="{E63E6C84-AAC4-4272-82DB-0820DBFF5E89}{1}{B0}" name="Delete numbered subtree" dateTime="2026-09-08T19:25:57.000Z" lastModifiedTime="2026-09-12T21:55:19.000Z" pageLevel="1"/><one:Page ID="{8C93E36A-F0C0-4425-A30B-B2B8A0719684}{1}{B0}" name="Indent bullet subtree" dateTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-12T21:55:19.000Z" pageLevel="1"/><one:Page ID="{789DA77C-5409-4002-B4AC-695817E35153}{1}{B0}" name="Outdent child across indentation gap" dateTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-12T21:55:19.000Z" pageLevel="1"/><one:Page ID="{A04B2BEA-D010-4C40-BAAC-F5BDB53D09E0}{1}{B0}" name="Move cell subtree down" dateTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-12T21:55:19.000Z" pageLevel="1"/></one:Section></one:Notebook> | 2 | <one:Notebook xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" name="notebook" nickname="notebook" ID="{B6FC55C7-91E6-4220-ACE0-617887D15952}{1}{B0}" path="C:\one-tests\runs\capture\notebook" lastModifiedTime="2026-09-26T06:39:56.000Z" color="#B49EDE"><one:Section name="synthetic" ID="{699BC697-816F-0570-1454-C5A7CDC73E3A}{1}{B0}" path="C:\one-tests\runs\capture\notebook\synthetic.one" lastModifiedTime="2026-09-26T06:39:56.000Z" color="#8AA8E4"><one:Page ID="{45D23511-08A4-4661-9333-6D3631FF2225}{1}{B0}" name="Bold 🦀 italic é color 東京" dateTime="2026-09-05T05:06:54.000Z" lastModifiedTime="2026-09-26T06:39:46.000Z" pageLevel="1"/><one:Page ID="{740D488E-DA7A-44D8-A7FD-A2E3E738B7C2}{1}{B0}" name="Indent first subtree" dateTime="2026-09-08T19:25:53.000Z" lastModifiedTime="2026-09-26T06:39:46.000Z" pageLevel="1"/><one:Page ID="{35F5E34E-FEF6-4DA1-A57D-0E7D5D808742}{1}{B0}" name="Outdent first group" dateTime="2026-09-08T19:25:55.000Z" lastModifiedTime="2026-09-26T06:39:46.000Z" pageLevel="1"/><one:Page ID="{02B11EAB-6EA5-4494-9559-A4451C8F46F2}{1}{B0}" name="Delete only grouped subtree" dateTime="2026-09-08T19:25:55.000Z" lastModifiedTime="2026-09-26T06:39:46.000Z" pageLevel="1"/><one:Page ID="{45C86EFC-D0BF-465E-92E7-1D94CA815CD9}{1}{B0}" name="Delete unindented sibling after group" dateTime="2026-09-08T19:25:55.000Z" lastModifiedTime="2026-09-26T06:39:46.000Z" pageLevel="1"/><one:Page ID="{E3DFE2CC-8F80-4980-91CA-5EE3D0C87070}{1}{B0}" name="Delete cell subtree" dateTime="2026-09-08T19:25:56.000Z" lastModifiedTime="2026-09-26T06:39:46.000Z" pageLevel="1"/><one:Page ID="{642FC171-1219-49C3-A843-D1EC76A916D9}{1}{B0}" name="Delete sole cell paragraph" dateTime="2026-09-08T19:25:56.000Z" lastModifiedTime="2026-09-26T06:39:46.000Z" pageLevel="1"/><one:Page ID="{D2520685-CF8A-4B44-8441-FE9760B36862}{1}{B0}" name="Move numbered subtree down" dateTime="2026-09-08T19:25:57.000Z" lastModifiedTime="2026-09-26T06:39:46.000Z" pageLevel="1"/><one:Page ID="{530286C8-5B7B-43A8-A9D9-5F3F90919AB2}{1}{B0}" name="Delete numbered subtree" dateTime="2026-09-08T19:25:57.000Z" lastModifiedTime="2026-09-26T06:39:46.000Z" pageLevel="1"/><one:Page ID="{39AF0926-017F-45FF-8809-E5A7EB1F52BF}{1}{B0}" name="Indent bullet subtree" dateTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-26T06:39:46.000Z" pageLevel="1"/><one:Page ID="{CDA14D30-A5B6-41D8-9FAE-3E475C8D9568}{1}{B0}" name="Outdent child across indentation gap" dateTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-26T06:39:46.000Z" pageLevel="1"/><one:Page ID="{1577C1A6-21AF-4D9A-91AE-A2A2FE53CDDB}{1}{B0}" name="Move cell subtree down" dateTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-26T06:39:46.000Z" pageLevel="1"/></one:Section></one:Notebook> |
corpus/table-edit/nested/cold/read/page-000.xml+3-3| ... | @@ -1,5 +1,5 @@ | ... | @@ -1,5 +1,5 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{56E30880-7E3F-485A-BAC8-09FC9BA6B44B}{1}{B0}" name="Delete cell subtree" dateTime="2026-09-08T19:25:56.000Z" lastModifiedTime="2026-09-12T21:55:19.000Z" pageLevel="1" lang="en-US"><one:TagDef index="0" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Preserved task"/><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-08T19:25:56.000Z" lastModifiedTime="2026-09-08T19:25:56.000Z" objectID="{C8D3C6B7-FB34-46C1-B4B5-3FE5439E350E}{33}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Delete cell subtree]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-12T21:53:40.000Z" objectID="{C8D3C6B7-FB34-46C1-B4B5-3FE5439E350E}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="428.1300048828125" height="62.30315780639648"/><one:OEChildren><one:OE creationTime="2026-09-08T19:25:56.000Z" lastModifiedTime="2026-09-12T21:53:40.000Z" objectID="{C8D3C6B7-FB34-46C1-B4B5-3FE5439E350E}{40}{B0}" alignment="left"><one:Table bordersVisible="true" lastModifiedTime="2026-09-12T21:53:40.000Z" objectID="{C8D3C6B7-FB34-46C1-B4B5-3FE5439E350E}{41}{B0}"><one:Columns><one:Column index="0" width="240.0" isLocked="true"/><one:Column index="1" width="180.0" isLocked="true"/></one:Columns><one:Row objectID="{C8D3C6B7-FB34-46C1-B4B5-3FE5439E350E}{42}{B0}" lastModifiedTime="2026-09-12T21:53:40.000Z"><one:Cell lastModifiedTime="2026-09-08T19:25:57.000Z" objectID="{C8D3C6B7-FB34-46C1-B4B5-3FE5439E350E}{43}{B0}" lastModifiedByInitials="V"><one:OEChildren><one:OE creationTime="2026-09-08T19:25:56.000Z" lastModifiedTime="2026-09-08T19:25:56.000Z" objectID="{C8D3C6B7-FB34-46C1-B4B5-3FE5439E350E}{46}{B0}" alignment="left" quickStyleIndex="0"><one:Tag index="0" completed="true" disabled="false" creationDate="2020-01-02T03:04:05.000Z" completionDate="2020-01-02T04:05:06.000Z"/><one:T><![CDATA[<span | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{02B11EAB-6EA5-4494-9559-A4451C8F46F2}{1}{B0}" name="Delete only grouped subtree" dateTime="2026-09-08T19:25:55.000Z" lastModifiedTime="2026-09-26T06:39:46.000Z" pageLevel="1" lang="en-US"><one:TagDef index="0" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Preserved task"/><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-08T19:25:55.000Z" lastModifiedTime="2026-09-08T19:25:55.000Z" objectID="{1DADDA58-BF77-4990-A3B0-DD48A8C24E06}{33}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Delete only grouped subtree]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T19:25:57.000Z" objectID="{1DADDA58-BF77-4990-A3B0-DD48A8C24E06}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="169.6107788085937" height="53.71086883544922"/><one:OEChildren indent="2"><one:OE creationTime="2026-09-08T19:25:55.000Z" lastModifiedTime="2026-09-08T19:25:55.000Z" objectID="{1DADDA58-BF77-4990-A3B0-DD48A8C24E06}{40}{B0}" alignment="left" quickStyleIndex="0"><one:Tag index="0" completed="true" disabled="false" creationDate="2020-01-02T03:04:05.000Z" completionDate="2020-01-02T04:05:06.000Z"/><one:T><![CDATA[<span |
| 3 | style='font-weight:bold' lang=en-US>Target &#129408; é</span><span lang=en-US> </span><a | 3 | style='font-weight:bold' lang=en-US>Target &#129408; é</span><span lang=en-US> </span><a |
| 4 | href="https://example.invalid/tree"><span lang=x-none>Link label</span></a>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-08T19:25:56.000Z" lastModifiedTime="2026-09-08T19:25:56.000Z" objectID="{C8D3C6B7-FB34-46C1-B4B5-3FE5439E350E}{55}{B0}" alignment="left" quickStyleIndex="0"><one:T style="font-family:Calibri;font-size:11.0pt"><![CDATA[<span | 4 | href="https://example.invalid/tree"><span lang=x-none>Link label</span></a>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-08T19:25:55.000Z" lastModifiedTime="2026-09-08T19:25:55.000Z" objectID="{1DADDA58-BF77-4990-A3B0-DD48A8C24E06}{49}{B0}" alignment="left" quickStyleIndex="0"><one:T style="font-family:Calibri;font-size:11.0pt"><![CDATA[<span |
| 5 | style='font-style:italic'>Child</span>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-08T19:25:56.000Z" lastModifiedTime="2026-09-08T19:25:56.000Z" objectID="{C8D3C6B7-FB34-46C1-B4B5-3FE5439E350E}{61}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Grandchild]]></one:T></one:OE></one:OEChildren></one:OE></one:OEChildren></one:OE><one:OE creationTime="2026-09-08T19:25:56.000Z" lastModifiedTime="2026-09-08T19:25:56.000Z" objectID="{C8D3C6B7-FB34-46C1-B4B5-3FE5439E350E}{66}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Cell tail]]></one:T></one:OE></one:OEChildren></one:Cell><one:Cell lastModifiedTime="2026-09-12T21:53:40.000Z" objectID="{C8D3C6B7-FB34-46C1-B4B5-3FE5439E350E}{71}{B0}" lastModifiedByInitials="V"><one:OEChildren><one:OE creationTime="2026-09-08T19:25:56.000Z" lastModifiedTime="2026-09-08T19:25:56.000Z" objectID="{C8D3C6B7-FB34-46C1-B4B5-3FE5439E350E}{74}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Other cell]]></one:T></one:OE><one:OE author="Table author" lastModifiedBy="Table author" creationTime="2026-09-12T21:53:40.000Z" lastModifiedTime="2026-09-12T21:53:40.000Z" objectID="{DFC0C49E-E046-0AFB-1BF8-ABC555E425B9}{0}{B0}" alignment="left" style="font-family:Calibri;font-size:11.0pt"><one:Table bordersVisible="true" lastModifiedTime="2026-09-12T21:53:40.000Z" objectID="{89987300-2101-0CE3-210D-9562C6C4ADB1}{0}{B0}"><one:Columns><one:Column index="0" width="72.0" isLocked="true"/></one:Columns><one:Row objectID="{CF32A2C1-04E7-0743-39C0-9C765A8E44CC}{0}{B0}" lastModifiedTime="2026-09-12T21:53:40.000Z"><one:Cell lastModifiedTime="2026-09-12T21:53:40.000Z" objectID="{1BAB3712-BACF-009D-0E46-EBA95350D396}{0}{B0}"><one:OEChildren><one:OE creationTime="2026-09-12T21:53:40.000Z" lastModifiedTime="2026-09-12T21:53:40.000Z" objectID="{82BC0B4C-FCA6-0778-2A66-7B5A5496D0A5}{0}{B0}" alignment="left"><one:T><![CDATA[Inner one]]></one:T></one:OE></one:OEChildren></one:Cell></one:Row><one:Row objectID="{A337C7AB-1858-0A2B-34E0-881F2933D0FB}{0}{B0}" lastModifiedTime="2026-09-12T21:53:40.000Z"><one:Cell lastModifiedTime="2026-09-12T21:53:40.000Z" objectID="{6E7D1347-DF88-06E2-392D-6543DAD4FBFE}{0}{B0}"><one:OEChildren><one:OE creationTime="2026-09-12T21:53:40.000Z" lastModifiedTime="2026-09-12T21:53:40.000Z" objectID="{5E489B68-01D9-0719-0234-7A5E46894E1A}{0}{B0}" alignment="left"><one:T><![CDATA[Inner two]]></one:T></one:OE></one:OEChildren></one:Cell></one:Row></one:Table></one:OE></one:OEChildren></one:Cell></one:Row></one:Table></one:OE></one:OEChildren></one:Outline><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T19:25:57.000Z" objectID="{C8D3C6B7-FB34-46C1-B4B5-3FE5439E350E}{79}{B0}"><one:Position x="540.0" y="108.0" z="1"/><one:Size width="99.85796356201171" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-08T19:25:56.000Z" lastModifiedTime="2026-09-08T19:25:56.000Z" objectID="{C8D3C6B7-FB34-46C1-B4B5-3FE5439E350E}{80}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Other outline]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> | 5 | style='font-style:italic'>Child</span>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-08T19:25:55.000Z" lastModifiedTime="2026-09-08T19:25:55.000Z" objectID="{1DADDA58-BF77-4990-A3B0-DD48A8C24E06}{55}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Grandchild]]></one:T></one:OE></one:OEChildren></one:OE></one:OEChildren></one:OE></one:OEChildren><one:OEChildren><one:OE creationTime="2026-09-08T19:25:55.000Z" lastModifiedTime="2026-09-08T19:25:55.000Z" objectID="{1DADDA58-BF77-4990-A3B0-DD48A8C24E06}{60}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Trailing sibling]]></one:T></one:OE></one:OEChildren></one:Outline><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T19:25:57.000Z" objectID="{1DADDA58-BF77-4990-A3B0-DD48A8C24E06}{66}{B0}"><one:Position x="540.0" y="108.0" z="1"/><one:Size width="99.85796356201171" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-08T19:25:55.000Z" lastModifiedTime="2026-09-08T19:25:55.000Z" objectID="{1DADDA58-BF77-4990-A3B0-DD48A8C24E06}{67}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Other outline]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/table-edit/nested/cold/read/page-001.xml+3-3| ... | @@ -1,5 +1,5 @@ | ... | @@ -1,5 +1,5 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{676EECC9-3E35-4A9E-AF43-A9882BDDAC59}{1}{B0}" name="Move numbered subtree down" dateTime="2026-09-08T19:25:57.000Z" lastModifiedTime="2026-09-12T21:55:19.000Z" pageLevel="1" lang="en-US"><one:TagDef index="0" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Preserved task"/><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-08T19:25:57.000Z" lastModifiedTime="2026-09-08T19:25:57.000Z" objectID="{0D5508DA-3F74-4EFD-8A2C-68449D01A988}{33}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Move numbered subtree down]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T19:26:01.000Z" objectID="{0D5508DA-3F74-4EFD-8A2C-68449D01A988}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="142.6107788085937" height="67.13858032226562"/><one:OEChildren><one:OE creationTime="2026-09-08T19:25:57.000Z" lastModifiedTime="2026-09-08T19:25:57.000Z" objectID="{0D5508DA-3F74-4EFD-8A2C-68449D01A988}{40}{B0}" alignment="left" quickStyleIndex="0"><one:List><one:Number numberSequence="1" numberFormat="##." fontSize="11.0" font="Calibri" text="I."/></one:List><one:T><![CDATA[Anchor]]></one:T></one:OE><one:OE creationTime="2026-09-08T19:25:57.000Z" lastModifiedTime="2026-09-08T19:25:57.000Z" objectID="{0D5508DA-3F74-4EFD-8A2C-68449D01A988}{46}{B0}" alignment="left" quickStyleIndex="0"><one:Tag index="0" completed="true" disabled="false" creationDate="2020-01-02T03:04:05.000Z" completionDate="2020-01-02T04:05:06.000Z"/><one:List><one:Number numberSequence="1" numberFormat="##." fontSize="11.0" font="Calibri" text="II."/></one:List><one:T><![CDATA[<span | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{1577C1A6-21AF-4D9A-91AE-A2A2FE53CDDB}{1}{B0}" name="Move cell subtree down" dateTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-26T06:39:46.000Z" pageLevel="1" lang="en-US"><one:TagDef index="0" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Preserved task"/><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{7E4295D4-7E60-4F12-BF8D-AA4BEAFCF664}{33}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Move cell subtree down]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T19:26:01.000Z" objectID="{7E4295D4-7E60-4F12-BF8D-AA4BEAFCF664}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="428.1300048828125" height="62.23086929321289"/><one:OEChildren><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:26:01.000Z" objectID="{7E4295D4-7E60-4F12-BF8D-AA4BEAFCF664}{40}{B0}" alignment="left"><one:Table bordersVisible="true" lastModifiedTime="2026-09-08T19:26:01.000Z" objectID="{7E4295D4-7E60-4F12-BF8D-AA4BEAFCF664}{41}{B0}"><one:Columns><one:Column index="0" width="240.0" isLocked="true"/><one:Column index="1" width="180.0" isLocked="true"/></one:Columns><one:Row objectID="{7E4295D4-7E60-4F12-BF8D-AA4BEAFCF664}{42}{B0}" lastModifiedTime="2026-09-08T19:26:01.000Z"><one:Cell lastModifiedTime="2026-09-08T19:26:01.000Z" objectID="{7E4295D4-7E60-4F12-BF8D-AA4BEAFCF664}{43}{B0}" lastModifiedByInitials="V"><one:OEChildren><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{7E4295D4-7E60-4F12-BF8D-AA4BEAFCF664}{46}{B0}" alignment="left" quickStyleIndex="0"><one:Tag index="0" completed="true" disabled="false" creationDate="2020-01-02T03:04:05.000Z" completionDate="2020-01-02T04:05:06.000Z"/><one:T><![CDATA[<span |
| 3 | style='font-weight:bold' lang=en-US>Target &#129408; é</span><span lang=en-US> </span><a | 3 | style='font-weight:bold' lang=en-US>Target &#129408; é</span><span lang=en-US> </span><a |
| 4 | href="https://example.invalid/tree"><span lang=x-none>Link label</span></a>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-08T19:25:57.000Z" lastModifiedTime="2026-09-08T19:25:57.000Z" objectID="{0D5508DA-3F74-4EFD-8A2C-68449D01A988}{55}{B0}" alignment="left" quickStyleIndex="0"><one:T style="font-family:Calibri;font-size:11.0pt"><![CDATA[<span | 4 | href="https://example.invalid/tree"><span lang=x-none>Link label</span></a>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{7E4295D4-7E60-4F12-BF8D-AA4BEAFCF664}{55}{B0}" alignment="left" quickStyleIndex="0"><one:T style="font-family:Calibri;font-size:11.0pt"><![CDATA[<span |
| 5 | style='font-style:italic'>Child</span>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-08T19:25:57.000Z" lastModifiedTime="2026-09-08T19:25:57.000Z" objectID="{0D5508DA-3F74-4EFD-8A2C-68449D01A988}{61}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Grandchild]]></one:T></one:OE></one:OEChildren></one:OE></one:OEChildren></one:OE><one:OE creationTime="2026-09-08T19:25:57.000Z" lastModifiedTime="2026-09-08T19:25:57.000Z" objectID="{0D5508DA-3F74-4EFD-8A2C-68449D01A988}{67}{B0}" alignment="left" quickStyleIndex="0"><one:List><one:Number numberSequence="1" numberFormat="##." fontSize="11.0" font="Calibri" text="III."/></one:List><one:T><![CDATA[Trailing sibling]]></one:T></one:OE></one:OEChildren></one:Outline><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T19:26:01.000Z" objectID="{0D5508DA-3F74-4EFD-8A2C-68449D01A988}{73}{B0}"><one:Position x="540.0" y="108.0" z="1"/><one:Size width="99.85796356201171" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-08T19:25:57.000Z" lastModifiedTime="2026-09-08T19:25:57.000Z" objectID="{0D5508DA-3F74-4EFD-8A2C-68449D01A988}{74}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Other outline]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> | 5 | style='font-style:italic'>Child</span>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{7E4295D4-7E60-4F12-BF8D-AA4BEAFCF664}{61}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Grandchild]]></one:T></one:OE></one:OEChildren></one:OE></one:OEChildren></one:OE><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{7E4295D4-7E60-4F12-BF8D-AA4BEAFCF664}{66}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Cell tail]]></one:T></one:OE></one:OEChildren></one:Cell><one:Cell lastModifiedTime="2026-09-08T19:26:01.000Z" objectID="{7E4295D4-7E60-4F12-BF8D-AA4BEAFCF664}{71}{B0}" lastModifiedByInitials="V"><one:OEChildren><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{7E4295D4-7E60-4F12-BF8D-AA4BEAFCF664}{74}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Other cell]]></one:T></one:OE></one:OEChildren></one:Cell></one:Row></one:Table></one:OE></one:OEChildren></one:Outline><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T19:26:01.000Z" objectID="{7E4295D4-7E60-4F12-BF8D-AA4BEAFCF664}{79}{B0}"><one:Position x="540.0" y="108.0" z="1"/><one:Size width="99.85796356201171" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{7E4295D4-7E60-4F12-BF8D-AA4BEAFCF664}{80}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Other outline]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/table-edit/nested/cold/read/page-002.xml+3-3| ... | @@ -1,5 +1,5 @@ | ... | @@ -1,5 +1,5 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{789DA77C-5409-4002-B4AC-695817E35153}{1}{B0}" name="Outdent child across indentation gap" dateTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-12T21:55:19.000Z" pageLevel="1" lang="en-US"><one:TagDef index="0" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Preserved task"/><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{5DAD8763-3535-46F5-8981-F32E9714936D}{33}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Outdent child across indentation gap]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T19:26:01.000Z" objectID="{5DAD8763-3535-46F5-8981-F32E9714936D}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="196.6107788085937" height="80.56629943847656"/><one:OEChildren><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{5DAD8763-3535-46F5-8981-F32E9714936D}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Anchor]]></one:T><one:OEChildren indent="2"><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{5DAD8763-3535-46F5-8981-F32E9714936D}{45}{B0}" alignment="left" quickStyleIndex="0"><one:Tag index="0" completed="true" disabled="false" creationDate="2020-01-02T03:04:05.000Z" completionDate="2020-01-02T04:05:06.000Z"/><one:T><![CDATA[<span | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{35F5E34E-FEF6-4DA1-A57D-0E7D5D808742}{1}{B0}" name="Outdent first group" dateTime="2026-09-08T19:25:55.000Z" lastModifiedTime="2026-09-26T06:39:46.000Z" pageLevel="1" lang="en-US"><one:TagDef index="0" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Preserved task"/><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-08T19:25:55.000Z" lastModifiedTime="2026-09-08T19:25:55.000Z" objectID="{AE8288C3-7617-4A49-9E4C-C628929357F8}{33}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Outdent first group]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T19:25:57.000Z" objectID="{AE8288C3-7617-4A49-9E4C-C628929357F8}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="169.6107788085937" height="53.71086883544922"/><one:OEChildren indent="2"><one:OE creationTime="2026-09-08T19:25:55.000Z" lastModifiedTime="2026-09-08T19:25:55.000Z" objectID="{AE8288C3-7617-4A49-9E4C-C628929357F8}{40}{B0}" alignment="left" quickStyleIndex="0"><one:Tag index="0" completed="true" disabled="false" creationDate="2020-01-02T03:04:05.000Z" completionDate="2020-01-02T04:05:06.000Z"/><one:T><![CDATA[<span |
| 3 | style='font-weight:bold' lang=en-US>Target &#129408; é</span><span lang=en-US> </span><a | 3 | style='font-weight:bold' lang=en-US>Target &#129408; é</span><span lang=en-US> </span><a |
| 4 | href="https://example.invalid/tree"><span lang=x-none>Link label</span></a>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{5DAD8763-3535-46F5-8981-F32E9714936D}{54}{B0}" alignment="left" quickStyleIndex="0"><one:T style="font-family:Calibri;font-size:11.0pt"><![CDATA[<span | 4 | href="https://example.invalid/tree"><span lang=x-none>Link label</span></a>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-08T19:25:55.000Z" lastModifiedTime="2026-09-08T19:25:55.000Z" objectID="{AE8288C3-7617-4A49-9E4C-C628929357F8}{49}{B0}" alignment="left" quickStyleIndex="0"><one:T style="font-family:Calibri;font-size:11.0pt"><![CDATA[<span |
| 5 | style='font-style:italic'>Child</span>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{5DAD8763-3535-46F5-8981-F32E9714936D}{60}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Grandchild]]></one:T></one:OE></one:OEChildren></one:OE></one:OEChildren></one:OE><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{5DAD8763-3535-46F5-8981-F32E9714936D}{65}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Other child]]></one:T></one:OE></one:OEChildren></one:OE><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{5DAD8763-3535-46F5-8981-F32E9714936D}{70}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Trailing sibling]]></one:T></one:OE></one:OEChildren></one:Outline><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T19:26:01.000Z" objectID="{5DAD8763-3535-46F5-8981-F32E9714936D}{76}{B0}"><one:Position x="540.0" y="108.0" z="1"/><one:Size width="99.85796356201171" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{5DAD8763-3535-46F5-8981-F32E9714936D}{77}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Other outline]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> | 5 | style='font-style:italic'>Child</span>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-08T19:25:55.000Z" lastModifiedTime="2026-09-08T19:25:55.000Z" objectID="{AE8288C3-7617-4A49-9E4C-C628929357F8}{55}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Grandchild]]></one:T></one:OE></one:OEChildren></one:OE></one:OEChildren></one:OE></one:OEChildren><one:OEChildren><one:OE creationTime="2026-09-08T19:25:55.000Z" lastModifiedTime="2026-09-08T19:25:55.000Z" objectID="{AE8288C3-7617-4A49-9E4C-C628929357F8}{60}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Trailing sibling]]></one:T></one:OE></one:OEChildren></one:Outline><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T19:25:57.000Z" objectID="{AE8288C3-7617-4A49-9E4C-C628929357F8}{66}{B0}"><one:Position x="540.0" y="108.0" z="1"/><one:Size width="99.85796356201171" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-08T19:25:55.000Z" lastModifiedTime="2026-09-08T19:25:55.000Z" objectID="{AE8288C3-7617-4A49-9E4C-C628929357F8}{67}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Other outline]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/table-edit/nested/cold/read/page-003.xml+3-3| ... | @@ -1,5 +1,5 @@ | ... | @@ -1,5 +1,5 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{80C90902-0F49-4C7B-8E7F-596216EE4379}{1}{B0}" name="Outdent first group" dateTime="2026-09-08T19:25:55.000Z" lastModifiedTime="2026-09-12T21:55:19.000Z" pageLevel="1" lang="en-US"><one:TagDef index="0" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Preserved task"/><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-08T19:25:55.000Z" lastModifiedTime="2026-09-08T19:25:55.000Z" objectID="{1BBE628F-87A8-4B93-B54E-9137D9FD93C3}{33}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Outdent first group]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T19:25:57.000Z" objectID="{1BBE628F-87A8-4B93-B54E-9137D9FD93C3}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="169.6107788085937" height="53.71086883544922"/><one:OEChildren indent="2"><one:OE creationTime="2026-09-08T19:25:55.000Z" lastModifiedTime="2026-09-08T19:25:55.000Z" objectID="{1BBE628F-87A8-4B93-B54E-9137D9FD93C3}{40}{B0}" alignment="left" quickStyleIndex="0"><one:Tag index="0" completed="true" disabled="false" creationDate="2020-01-02T03:04:05.000Z" completionDate="2020-01-02T04:05:06.000Z"/><one:T><![CDATA[<span | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{39AF0926-017F-45FF-8809-E5A7EB1F52BF}{1}{B0}" name="Indent bullet subtree" dateTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-26T06:39:46.000Z" pageLevel="1" lang="en-US"><one:TagDef index="0" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Preserved task"/><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{00847BD4-E464-4DDD-945A-2947FB4E31B2}{33}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Indent bullet subtree]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T19:26:01.000Z" objectID="{00847BD4-E464-4DDD-945A-2947FB4E31B2}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="142.6107788085937" height="67.13858032226562"/><one:OEChildren><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{00847BD4-E464-4DDD-945A-2947FB4E31B2}{40}{B0}" alignment="left" quickStyleIndex="0"><one:List><one:Bullet bullet="3" fontSize="11.0"/></one:List><one:T><![CDATA[Anchor]]></one:T></one:OE><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{00847BD4-E464-4DDD-945A-2947FB4E31B2}{46}{B0}" alignment="left" quickStyleIndex="0"><one:Tag index="0" completed="true" disabled="false" creationDate="2020-01-02T03:04:05.000Z" completionDate="2020-01-02T04:05:06.000Z"/><one:List><one:Bullet bullet="3" fontSize="11.0"/></one:List><one:T><![CDATA[<span |
| 3 | style='font-weight:bold' lang=en-US>Target &#129408; é</span><span lang=en-US> </span><a | 3 | style='font-weight:bold' lang=en-US>Target &#129408; é</span><span lang=en-US> </span><a |
| 4 | href="https://example.invalid/tree"><span lang=x-none>Link label</span></a>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-08T19:25:55.000Z" lastModifiedTime="2026-09-08T19:25:55.000Z" objectID="{1BBE628F-87A8-4B93-B54E-9137D9FD93C3}{49}{B0}" alignment="left" quickStyleIndex="0"><one:T style="font-family:Calibri;font-size:11.0pt"><![CDATA[<span | 4 | href="https://example.invalid/tree"><span lang=x-none>Link label</span></a>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{00847BD4-E464-4DDD-945A-2947FB4E31B2}{55}{B0}" alignment="left" quickStyleIndex="0"><one:T style="font-family:Calibri;font-size:11.0pt"><![CDATA[<span |
| 5 | style='font-style:italic'>Child</span>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-08T19:25:55.000Z" lastModifiedTime="2026-09-08T19:25:55.000Z" objectID="{1BBE628F-87A8-4B93-B54E-9137D9FD93C3}{55}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Grandchild]]></one:T></one:OE></one:OEChildren></one:OE></one:OEChildren></one:OE></one:OEChildren><one:OEChildren><one:OE creationTime="2026-09-08T19:25:55.000Z" lastModifiedTime="2026-09-08T19:25:55.000Z" objectID="{1BBE628F-87A8-4B93-B54E-9137D9FD93C3}{60}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Trailing sibling]]></one:T></one:OE></one:OEChildren></one:Outline><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T19:25:57.000Z" objectID="{1BBE628F-87A8-4B93-B54E-9137D9FD93C3}{66}{B0}"><one:Position x="540.0" y="108.0" z="1"/><one:Size width="99.85796356201171" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-08T19:25:55.000Z" lastModifiedTime="2026-09-08T19:25:55.000Z" objectID="{1BBE628F-87A8-4B93-B54E-9137D9FD93C3}{67}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Other outline]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> | 5 | style='font-style:italic'>Child</span>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{00847BD4-E464-4DDD-945A-2947FB4E31B2}{61}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Grandchild]]></one:T></one:OE></one:OEChildren></one:OE></one:OEChildren></one:OE><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{00847BD4-E464-4DDD-945A-2947FB4E31B2}{67}{B0}" alignment="left" quickStyleIndex="0"><one:List><one:Bullet bullet="3" fontSize="11.0"/></one:List><one:T><![CDATA[Trailing sibling]]></one:T></one:OE></one:OEChildren></one:Outline><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T19:26:01.000Z" objectID="{00847BD4-E464-4DDD-945A-2947FB4E31B2}{73}{B0}"><one:Position x="540.0" y="108.0" z="1"/><one:Size width="99.85796356201171" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{00847BD4-E464-4DDD-945A-2947FB4E31B2}{74}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Other outline]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/table-edit/nested/cold/read/page-004.xml+2-3| ... | @@ -1,5 +1,4 @@ | ... | @@ -1,5 +1,4 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{8C93E36A-F0C0-4425-A30B-B2B8A0719684}{1}{B0}" name="Indent bullet subtree" dateTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-12T21:55:19.000Z" pageLevel="1" lang="en-US"><one:TagDef index="0" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Preserved task"/><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{B5B89198-15DB-4C07-BF58-7E58B020F589}{33}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Indent bullet subtree]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T19:26:01.000Z" objectID="{B5B89198-15DB-4C07-BF58-7E58B020F589}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="142.6107788085937" height="67.13858032226562"/><one:OEChildren><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{B5B89198-15DB-4C07-BF58-7E58B020F589}{40}{B0}" alignment="left" quickStyleIndex="0"><one:List><one:Bullet bullet="3" fontSize="11.0"/></one:List><one:T><![CDATA[Anchor]]></one:T></one:OE><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{B5B89198-15DB-4C07-BF58-7E58B020F589}{46}{B0}" alignment="left" quickStyleIndex="0"><one:Tag index="0" completed="true" disabled="false" creationDate="2020-01-02T03:04:05.000Z" completionDate="2020-01-02T04:05:06.000Z"/><one:List><one:Bullet bullet="3" fontSize="11.0"/></one:List><one:T><![CDATA[<span | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{45C86EFC-D0BF-465E-92E7-1D94CA815CD9}{1}{B0}" name="Delete unindented sibling after group" dateTime="2026-09-08T19:25:55.000Z" lastModifiedTime="2026-09-26T06:39:46.000Z" pageLevel="1" lang="en-US"><one:TagDef index="0" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Preserved task"/><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-08T19:25:56.000Z" lastModifiedTime="2026-09-08T19:25:56.000Z" objectID="{349AC90C-FE91-4368-BC4B-E202977EB986}{33}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Delete unindented sibling after group]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T19:25:57.000Z" objectID="{349AC90C-FE91-4368-BC4B-E202977EB986}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="129.6621246337891" height="26.85543441772461"/><one:OEChildren indent="2"><one:OE creationTime="2026-09-08T19:25:56.000Z" lastModifiedTime="2026-09-08T19:25:56.000Z" objectID="{349AC90C-FE91-4368-BC4B-E202977EB986}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Anchor]]></one:T></one:OE></one:OEChildren><one:OEChildren><one:OE creationTime="2026-09-08T19:25:56.000Z" lastModifiedTime="2026-09-08T19:25:56.000Z" objectID="{349AC90C-FE91-4368-BC4B-E202977EB986}{45}{B0}" alignment="left" quickStyleIndex="0"><one:Tag index="0" completed="true" disabled="false" creationDate="2020-01-02T03:04:05.000Z" completionDate="2020-01-02T04:05:06.000Z"/><one:T><![CDATA[<span |
| 3 | style='font-weight:bold' lang=en-US>Target &#129408; é</span><span lang=en-US> </span><a | 3 | style='font-weight:bold' lang=en-US>Target &#129408; é</span><span lang=en-US> </span><a |
| 4 | href="https://example.invalid/tree"><span lang=x-none>Link label</span></a>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{B5B89198-15DB-4C07-BF58-7E58B020F589}{55}{B0}" alignment="left" quickStyleIndex="0"><one:T style="font-family:Calibri;font-size:11.0pt"><![CDATA[<span | 4 | href="https://example.invalid/tree"><span lang=x-none>Link label</span></a>]]></one:T></one:OE></one:OEChildren></one:Outline><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T19:25:57.000Z" objectID="{349AC90C-FE91-4368-BC4B-E202977EB986}{55}{B0}"><one:Position x="540.0" y="108.0" z="1"/><one:Size width="99.85796356201171" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-08T19:25:56.000Z" lastModifiedTime="2026-09-08T19:25:56.000Z" objectID="{349AC90C-FE91-4368-BC4B-E202977EB986}{56}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Other outline]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
| 5 | style='font-style:italic'>Child</span>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{B5B89198-15DB-4C07-BF58-7E58B020F589}{61}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Grandchild]]></one:T></one:OE></one:OEChildren></one:OE></one:OEChildren></one:OE><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{B5B89198-15DB-4C07-BF58-7E58B020F589}{67}{B0}" alignment="left" quickStyleIndex="0"><one:List><one:Bullet bullet="3" fontSize="11.0"/></one:List><one:T><![CDATA[Trailing sibling]]></one:T></one:OE></one:OEChildren></one:Outline><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T19:26:01.000Z" objectID="{B5B89198-15DB-4C07-BF58-7E58B020F589}{73}{B0}"><one:Position x="540.0" y="108.0" z="1"/><one:Size width="99.85796356201171" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{B5B89198-15DB-4C07-BF58-7E58B020F589}{74}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Other outline]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/table-edit/nested/cold/read/page-005.xml+19-4| ... | @@ -1,5 +1,20 @@ | ... | @@ -1,5 +1,20 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{A04B2BEA-D010-4C40-BAAC-F5BDB53D09E0}{1}{B0}" name="Move cell subtree down" dateTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-12T21:55:19.000Z" pageLevel="1" lang="en-US"><one:TagDef index="0" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Preserved task"/><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{CB7E7F98-8FDF-4EC8-948F-FD54A192325F}{33}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Move cell subtree down]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T19:26:01.000Z" objectID="{CB7E7F98-8FDF-4EC8-948F-FD54A192325F}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="428.1300048828125" height="62.23086929321289"/><one:OEChildren><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:26:01.000Z" objectID="{CB7E7F98-8FDF-4EC8-948F-FD54A192325F}{40}{B0}" alignment="left"><one:Table bordersVisible="true" lastModifiedTime="2026-09-08T19:26:01.000Z" objectID="{CB7E7F98-8FDF-4EC8-948F-FD54A192325F}{41}{B0}"><one:Columns><one:Column index="0" width="240.0" isLocked="true"/><one:Column index="1" width="180.0" isLocked="true"/></one:Columns><one:Row objectID="{CB7E7F98-8FDF-4EC8-948F-FD54A192325F}{42}{B0}" lastModifiedTime="2026-09-08T19:26:01.000Z"><one:Cell lastModifiedTime="2026-09-08T19:26:01.000Z" objectID="{CB7E7F98-8FDF-4EC8-948F-FD54A192325F}{43}{B0}" lastModifiedByInitials="V"><one:OEChildren><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{CB7E7F98-8FDF-4EC8-948F-FD54A192325F}{46}{B0}" alignment="left" quickStyleIndex="0"><one:Tag index="0" completed="true" disabled="false" creationDate="2020-01-02T03:04:05.000Z" completionDate="2020-01-02T04:05:06.000Z"/><one:T><![CDATA[<span | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{45D23511-08A4-4661-9333-6D3631FF2225}{1}{B0}" name="Bold 🦀 italic é color 東京" dateTime="2026-09-05T05:06:54.000Z" lastModifiedTime="2026-09-26T06:39:46.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Outline author="Rich author" lastModifiedBy="Rich author" lastModifiedTime="2026-09-08T04:47:24.000Z" objectID="{221E9DAD-43D1-45D7-9CC1-E1EDCE63C744}{10}{B0}"><one:Position x="36.0" y="14.40000057220459" z="0"/><one:Size width="127.5136947631836" height="42.90299224853515"/><one:OEChildren><one:OE creationTime="2026-09-08T04:47:24.000Z" lastModifiedTime="2026-09-08T04:47:24.000Z" objectID="{257B355A-B0AC-0EDD-2C95-22E70508B5B2}{1}{B0}" alignment="left"><one:T><![CDATA[<span style='font-weight:bold; |
| 3 | style='font-weight:bold' lang=en-US>Target &#129408; é</span><span lang=en-US> </span><a | 3 | text-decoration:underline;font-family:Calibri;font-size:11.0pt'>Bold</span><span |
| 4 | href="https://example.invalid/tree"><span lang=x-none>Link label</span></a>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{CB7E7F98-8FDF-4EC8-948F-FD54A192325F}{55}{B0}" alignment="left" quickStyleIndex="0"><one:T style="font-family:Calibri;font-size:11.0pt"><![CDATA[<span | 4 | style='font-family:Calibri;font-size:11.0pt'> </span><span style='font-family: |
| 5 | style='font-style:italic'>Child</span>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{CB7E7F98-8FDF-4EC8-948F-FD54A192325F}{61}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Grandchild]]></one:T></one:OE></one:OEChildren></one:OE></one:OEChildren></one:OE><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{CB7E7F98-8FDF-4EC8-948F-FD54A192325F}{66}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Cell tail]]></one:T></one:OE></one:OEChildren></one:Cell><one:Cell lastModifiedTime="2026-09-08T19:26:01.000Z" objectID="{CB7E7F98-8FDF-4EC8-948F-FD54A192325F}{71}{B0}" lastModifiedByInitials="V"><one:OEChildren><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{CB7E7F98-8FDF-4EC8-948F-FD54A192325F}{74}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Other cell]]></one:T></one:OE></one:OEChildren></one:Cell></one:Row></one:Table></one:OE></one:OEChildren></one:Outline><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T19:26:01.000Z" objectID="{CB7E7F98-8FDF-4EC8-948F-FD54A192325F}{79}{B0}"><one:Position x="540.0" y="108.0" z="1"/><one:Size width="99.85796356201171" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{CB7E7F98-8FDF-4EC8-948F-FD54A192325F}{80}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Other outline]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> | 5 | Arial;font-size:13.5pt'>&#129408;</span><span style='font-family:Calibri; |
| 6 | font-size:11.0pt'> </span><span style='font-style:italic;text-decoration:line-through; | ||
| 7 | font-family:Calibri;font-size:11.0pt'>italic</span><span style='font-family: | ||
| 8 | Calibri;font-size:11.0pt'> </span><span style='vertical-align:super;font-family: | ||
| 9 | Calibri;font-size:11.0pt'>é</span><span style='font-family:Calibri;font-size: | ||
| 10 | 11.0pt'> </span><span style='font-family:Calibri;font-size:11.0pt;color:#0C2238; | ||
| 11 | background:yellow;mso-highlight:yellow'>color</span><span style='font-family: | ||
| 12 | Calibri;font-size:11.0pt'> </span><span style='vertical-align:sub;font-family: | ||
| 13 | Calibri;font-size:11.0pt'>東京</span><span style='font-family:Calibri;font-size: | ||
| 14 | 11.0pt'><br> | ||
| 15 | End</span>]]></one:T></one:OE><one:OE author="snow" authorInitials="S" lastModifiedBy="snow" lastModifiedByInitials="S" creationTime="2026-09-05T05:06:55.000Z" lastModifiedTime="2026-09-05T05:06:56.000Z" objectID="{221E9DAD-43D1-45D7-9CC1-E1EDCE63C744}{11}{B0}" alignment="left" quickStyleIndex="0" style="font-size:11.0pt;color:#1F4E79"><one:T><![CDATA[<span | ||
| 16 | style='font-weight:bold;font-family:Calibri' lang=en-US>Fictitious: café, </span><span | ||
| 17 | style='font-weight:bold;font-family:SimSun' lang=en-US>東京</span><span | ||
| 18 | style='font-weight:bold;font-family:Calibri' lang=en-US>, </span><span | ||
| 19 | style='font-weight:bold;font-family:Arial;direction:rtl;unicode-bidi:embed' | ||
| 20 | lang=ar-SA>مرحبا</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/table-edit/nested/cold/read/page-006.xml+3-3| ... | @@ -1,5 +1,5 @@ | ... | @@ -1,5 +1,5 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{B78DF4E7-9F1A-454E-BE5B-F35A57E182C9}{1}{B0}" name="Delete only grouped subtree" dateTime="2026-09-08T19:25:55.000Z" lastModifiedTime="2026-09-12T21:55:19.000Z" pageLevel="1" lang="en-US"><one:TagDef index="0" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Preserved task"/><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-08T19:25:55.000Z" lastModifiedTime="2026-09-08T19:25:55.000Z" objectID="{A8913014-4EC8-484A-88B2-8A57E3AC8A3D}{33}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Delete only grouped subtree]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T19:25:57.000Z" objectID="{A8913014-4EC8-484A-88B2-8A57E3AC8A3D}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="169.6107788085937" height="53.71086883544922"/><one:OEChildren indent="2"><one:OE creationTime="2026-09-08T19:25:55.000Z" lastModifiedTime="2026-09-08T19:25:55.000Z" objectID="{A8913014-4EC8-484A-88B2-8A57E3AC8A3D}{40}{B0}" alignment="left" quickStyleIndex="0"><one:Tag index="0" completed="true" disabled="false" creationDate="2020-01-02T03:04:05.000Z" completionDate="2020-01-02T04:05:06.000Z"/><one:T><![CDATA[<span | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{530286C8-5B7B-43A8-A9D9-5F3F90919AB2}{1}{B0}" name="Delete numbered subtree" dateTime="2026-09-08T19:25:57.000Z" lastModifiedTime="2026-09-26T06:39:46.000Z" pageLevel="1" lang="en-US"><one:TagDef index="0" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Preserved task"/><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{6DA0CF5F-713E-4AE2-86D9-8366208BBF56}{33}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Delete numbered subtree]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T19:26:01.000Z" objectID="{6DA0CF5F-713E-4AE2-86D9-8366208BBF56}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="142.6107788085937" height="67.13858032226562"/><one:OEChildren><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{6DA0CF5F-713E-4AE2-86D9-8366208BBF56}{40}{B0}" alignment="left" quickStyleIndex="0"><one:List><one:Number numberSequence="1" numberFormat="##." fontSize="11.0" font="Calibri" text="I."/></one:List><one:T><![CDATA[Anchor]]></one:T></one:OE><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{6DA0CF5F-713E-4AE2-86D9-8366208BBF56}{46}{B0}" alignment="left" quickStyleIndex="0"><one:Tag index="0" completed="true" disabled="false" creationDate="2020-01-02T03:04:05.000Z" completionDate="2020-01-02T04:05:06.000Z"/><one:List><one:Number numberSequence="1" numberFormat="##." fontSize="11.0" font="Calibri" text="II."/></one:List><one:T><![CDATA[<span |
| 3 | style='font-weight:bold' lang=en-US>Target &#129408; é</span><span lang=en-US> </span><a | 3 | style='font-weight:bold' lang=en-US>Target &#129408; é</span><span lang=en-US> </span><a |
| 4 | href="https://example.invalid/tree"><span lang=x-none>Link label</span></a>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-08T19:25:55.000Z" lastModifiedTime="2026-09-08T19:25:55.000Z" objectID="{A8913014-4EC8-484A-88B2-8A57E3AC8A3D}{49}{B0}" alignment="left" quickStyleIndex="0"><one:T style="font-family:Calibri;font-size:11.0pt"><![CDATA[<span | 4 | href="https://example.invalid/tree"><span lang=x-none>Link label</span></a>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{6DA0CF5F-713E-4AE2-86D9-8366208BBF56}{55}{B0}" alignment="left" quickStyleIndex="0"><one:T style="font-family:Calibri;font-size:11.0pt"><![CDATA[<span |
| 5 | style='font-style:italic'>Child</span>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-08T19:25:55.000Z" lastModifiedTime="2026-09-08T19:25:55.000Z" objectID="{A8913014-4EC8-484A-88B2-8A57E3AC8A3D}{55}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Grandchild]]></one:T></one:OE></one:OEChildren></one:OE></one:OEChildren></one:OE></one:OEChildren><one:OEChildren><one:OE creationTime="2026-09-08T19:25:55.000Z" lastModifiedTime="2026-09-08T19:25:55.000Z" objectID="{A8913014-4EC8-484A-88B2-8A57E3AC8A3D}{60}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Trailing sibling]]></one:T></one:OE></one:OEChildren></one:Outline><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T19:25:57.000Z" objectID="{A8913014-4EC8-484A-88B2-8A57E3AC8A3D}{66}{B0}"><one:Position x="540.0" y="108.0" z="1"/><one:Size width="99.85796356201171" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-08T19:25:55.000Z" lastModifiedTime="2026-09-08T19:25:55.000Z" objectID="{A8913014-4EC8-484A-88B2-8A57E3AC8A3D}{67}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Other outline]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> | 5 | style='font-style:italic'>Child</span>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{6DA0CF5F-713E-4AE2-86D9-8366208BBF56}{61}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Grandchild]]></one:T></one:OE></one:OEChildren></one:OE></one:OEChildren></one:OE><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{6DA0CF5F-713E-4AE2-86D9-8366208BBF56}{67}{B0}" alignment="left" quickStyleIndex="0"><one:List><one:Number numberSequence="1" numberFormat="##." fontSize="11.0" font="Calibri" text="III."/></one:List><one:T><![CDATA[Trailing sibling]]></one:T></one:OE></one:OEChildren></one:Outline><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T19:26:01.000Z" objectID="{6DA0CF5F-713E-4AE2-86D9-8366208BBF56}{73}{B0}"><one:Position x="540.0" y="108.0" z="1"/><one:Size width="99.85796356201171" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{6DA0CF5F-713E-4AE2-86D9-8366208BBF56}{74}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Other outline]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/table-edit/nested/cold/read/page-007.xml+2-3| ... | @@ -1,5 +1,4 @@ | ... | @@ -1,5 +1,4 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{C131A2C2-2BC5-4502-8CFF-F5FCAC5673F9}{1}{B0}" name="Indent first subtree" dateTime="2026-09-08T19:25:53.000Z" lastModifiedTime="2026-09-12T21:55:19.000Z" pageLevel="1" lang="en-US"><one:TagDef index="0" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Preserved task"/><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-08T19:25:55.000Z" lastModifiedTime="2026-09-08T19:25:55.000Z" objectID="{0E6415D5-ECAA-442C-AED2-B0701782A11F}{33}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Indent first subtree]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T19:25:57.000Z" objectID="{0E6415D5-ECAA-442C-AED2-B0701782A11F}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="142.6107788085937" height="53.71086883544922"/><one:OEChildren><one:OE creationTime="2026-09-08T19:25:54.000Z" lastModifiedTime="2026-09-08T19:25:55.000Z" objectID="{0E6415D5-ECAA-442C-AED2-B0701782A11F}{40}{B0}" alignment="left" quickStyleIndex="0"><one:Tag index="0" completed="true" disabled="false" creationDate="2020-01-02T03:04:05.000Z" completionDate="2020-01-02T04:05:06.000Z"/><one:T><![CDATA[<span | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{642FC171-1219-49C3-A843-D1EC76A916D9}{1}{B0}" name="Delete sole cell paragraph" dateTime="2026-09-08T19:25:56.000Z" lastModifiedTime="2026-09-26T06:39:46.000Z" pageLevel="1" lang="en-US"><one:TagDef index="0" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Preserved task"/><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-08T19:25:57.000Z" lastModifiedTime="2026-09-08T19:25:57.000Z" objectID="{8F51E7BC-E879-45ED-8C29-9A3216E01E8C}{17}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Delete sole cell paragraph]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T19:26:01.000Z" objectID="{8F51E7BC-E879-45ED-8C29-9A3216E01E8C}{23}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="428.1300048828125" height="21.94771385192871"/><one:OEChildren><one:OE creationTime="2026-09-08T19:25:57.000Z" lastModifiedTime="2026-09-08T19:26:01.000Z" objectID="{8F51E7BC-E879-45ED-8C29-9A3216E01E8C}{24}{B0}" alignment="left"><one:Table bordersVisible="true" lastModifiedTime="2026-09-08T19:26:01.000Z" objectID="{8F51E7BC-E879-45ED-8C29-9A3216E01E8C}{25}{B0}"><one:Columns><one:Column index="0" width="240.0" isLocked="true"/><one:Column index="1" width="180.0" isLocked="true"/></one:Columns><one:Row objectID="{8F51E7BC-E879-45ED-8C29-9A3216E01E8C}{26}{B0}" lastModifiedTime="2026-09-08T19:26:01.000Z"><one:Cell lastModifiedTime="2026-09-08T19:26:01.000Z" objectID="{8F51E7BC-E879-45ED-8C29-9A3216E01E8C}{27}{B0}" lastModifiedByInitials="V"><one:OEChildren><one:OE creationTime="2026-09-08T19:25:57.000Z" lastModifiedTime="2026-09-08T19:25:57.000Z" objectID="{8F51E7BC-E879-45ED-8C29-9A3216E01E8C}{30}{B0}" alignment="left" quickStyleIndex="0"><one:Tag index="0" completed="true" disabled="false" creationDate="2020-01-02T03:04:05.000Z" completionDate="2020-01-02T04:05:06.000Z"/><one:T><![CDATA[<span |
| 3 | style='font-weight:bold' lang=en-US>Target &#129408; é</span><span lang=en-US> </span><a | 3 | style='font-weight:bold' lang=en-US>Target &#129408; é</span><span lang=en-US> </span><a |
| 4 | href="https://example.invalid/tree"><span lang=x-none>Link label</span></a>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-08T19:25:55.000Z" lastModifiedTime="2026-09-08T19:25:55.000Z" objectID="{0E6415D5-ECAA-442C-AED2-B0701782A11F}{49}{B0}" alignment="left" quickStyleIndex="0"><one:T style="font-family:Calibri;font-size:11.0pt"><![CDATA[<span | 4 | href="https://example.invalid/tree"><span lang=x-none>Link label</span></a>]]></one:T></one:OE></one:OEChildren></one:Cell><one:Cell lastModifiedTime="2026-09-08T19:26:01.000Z" objectID="{8F51E7BC-E879-45ED-8C29-9A3216E01E8C}{39}{B0}" lastModifiedByInitials="V"><one:OEChildren><one:OE creationTime="2026-09-08T19:25:57.000Z" lastModifiedTime="2026-09-08T19:25:57.000Z" objectID="{8F51E7BC-E879-45ED-8C29-9A3216E01E8C}{42}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Other cell]]></one:T></one:OE></one:OEChildren></one:Cell></one:Row></one:Table></one:OE></one:OEChildren></one:Outline><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T19:26:01.000Z" objectID="{8F51E7BC-E879-45ED-8C29-9A3216E01E8C}{47}{B0}"><one:Position x="540.0" y="108.0" z="1"/><one:Size width="99.85796356201171" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-08T19:25:57.000Z" lastModifiedTime="2026-09-08T19:25:57.000Z" objectID="{8F51E7BC-E879-45ED-8C29-9A3216E01E8C}{48}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Other outline]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
| 5 | style='font-style:italic'>Child</span>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-08T19:25:55.000Z" lastModifiedTime="2026-09-08T19:25:55.000Z" objectID="{0E6415D5-ECAA-442C-AED2-B0701782A11F}{55}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Grandchild]]></one:T></one:OE></one:OEChildren></one:OE></one:OEChildren></one:OE><one:OE creationTime="2026-09-08T19:25:55.000Z" lastModifiedTime="2026-09-08T19:25:55.000Z" objectID="{0E6415D5-ECAA-442C-AED2-B0701782A11F}{60}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Trailing sibling]]></one:T></one:OE></one:OEChildren></one:Outline><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T19:25:57.000Z" objectID="{0E6415D5-ECAA-442C-AED2-B0701782A11F}{65}{B0}"><one:Position x="540.0" y="108.0" z="1"/><one:Size width="99.85796356201171" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-08T19:25:55.000Z" lastModifiedTime="2026-09-08T19:25:55.000Z" objectID="{0E6415D5-ECAA-442C-AED2-B0701782A11F}{66}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Other outline]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/table-edit/nested/cold/read/page-008.xml+3-2| ... | @@ -1,4 +1,5 @@ | ... | @@ -1,4 +1,5 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{D1132B3D-E3A6-4819-8341-86F33DC7D2E2}{1}{B0}" name="Delete sole cell paragraph" dateTime="2026-09-08T19:25:56.000Z" lastModifiedTime="2026-09-12T21:55:19.000Z" pageLevel="1" lang="en-US"><one:TagDef index="0" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Preserved task"/><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-08T19:25:57.000Z" lastModifiedTime="2026-09-08T19:25:57.000Z" objectID="{3A6D0DF0-19C6-4437-A72B-CD2D5D8EDAB7}{17}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Delete sole cell paragraph]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T19:26:01.000Z" objectID="{3A6D0DF0-19C6-4437-A72B-CD2D5D8EDAB7}{23}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="428.1300048828125" height="21.94771385192871"/><one:OEChildren><one:OE creationTime="2026-09-08T19:25:57.000Z" lastModifiedTime="2026-09-08T19:26:01.000Z" objectID="{3A6D0DF0-19C6-4437-A72B-CD2D5D8EDAB7}{24}{B0}" alignment="left"><one:Table bordersVisible="true" lastModifiedTime="2026-09-08T19:26:01.000Z" objectID="{3A6D0DF0-19C6-4437-A72B-CD2D5D8EDAB7}{25}{B0}"><one:Columns><one:Column index="0" width="240.0" isLocked="true"/><one:Column index="1" width="180.0" isLocked="true"/></one:Columns><one:Row objectID="{3A6D0DF0-19C6-4437-A72B-CD2D5D8EDAB7}{26}{B0}" lastModifiedTime="2026-09-08T19:26:01.000Z"><one:Cell lastModifiedTime="2026-09-08T19:26:01.000Z" objectID="{3A6D0DF0-19C6-4437-A72B-CD2D5D8EDAB7}{27}{B0}" lastModifiedByInitials="V"><one:OEChildren><one:OE creationTime="2026-09-08T19:25:57.000Z" lastModifiedTime="2026-09-08T19:25:57.000Z" objectID="{3A6D0DF0-19C6-4437-A72B-CD2D5D8EDAB7}{30}{B0}" alignment="left" quickStyleIndex="0"><one:Tag index="0" completed="true" disabled="false" creationDate="2020-01-02T03:04:05.000Z" completionDate="2020-01-02T04:05:06.000Z"/><one:T><![CDATA[<span | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{740D488E-DA7A-44D8-A7FD-A2E3E738B7C2}{1}{B0}" name="Indent first subtree" dateTime="2026-09-08T19:25:53.000Z" lastModifiedTime="2026-09-26T06:39:46.000Z" pageLevel="1" lang="en-US"><one:TagDef index="0" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Preserved task"/><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-08T19:25:55.000Z" lastModifiedTime="2026-09-08T19:25:55.000Z" objectID="{BB58FF99-1D15-45F6-85D0-E76F5CEC6524}{33}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Indent first subtree]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T19:25:57.000Z" objectID="{BB58FF99-1D15-45F6-85D0-E76F5CEC6524}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="142.6107788085937" height="53.71086883544922"/><one:OEChildren><one:OE creationTime="2026-09-08T19:25:54.000Z" lastModifiedTime="2026-09-08T19:25:55.000Z" objectID="{BB58FF99-1D15-45F6-85D0-E76F5CEC6524}{40}{B0}" alignment="left" quickStyleIndex="0"><one:Tag index="0" completed="true" disabled="false" creationDate="2020-01-02T03:04:05.000Z" completionDate="2020-01-02T04:05:06.000Z"/><one:T><![CDATA[<span |
| 3 | style='font-weight:bold' lang=en-US>Target &#129408; é</span><span lang=en-US> </span><a | 3 | style='font-weight:bold' lang=en-US>Target &#129408; é</span><span lang=en-US> </span><a |
| 4 | href="https://example.invalid/tree"><span lang=x-none>Link label</span></a>]]></one:T></one:OE></one:OEChildren></one:Cell><one:Cell lastModifiedTime="2026-09-08T19:26:01.000Z" objectID="{3A6D0DF0-19C6-4437-A72B-CD2D5D8EDAB7}{39}{B0}" lastModifiedByInitials="V"><one:OEChildren><one:OE creationTime="2026-09-08T19:25:57.000Z" lastModifiedTime="2026-09-08T19:25:57.000Z" objectID="{3A6D0DF0-19C6-4437-A72B-CD2D5D8EDAB7}{42}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Other cell]]></one:T></one:OE></one:OEChildren></one:Cell></one:Row></one:Table></one:OE></one:OEChildren></one:Outline><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T19:26:01.000Z" objectID="{3A6D0DF0-19C6-4437-A72B-CD2D5D8EDAB7}{47}{B0}"><one:Position x="540.0" y="108.0" z="1"/><one:Size width="99.85796356201171" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-08T19:25:57.000Z" lastModifiedTime="2026-09-08T19:25:57.000Z" objectID="{3A6D0DF0-19C6-4437-A72B-CD2D5D8EDAB7}{48}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Other outline]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> | 4 | href="https://example.invalid/tree"><span lang=x-none>Link label</span></a>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-08T19:25:55.000Z" lastModifiedTime="2026-09-08T19:25:55.000Z" objectID="{BB58FF99-1D15-45F6-85D0-E76F5CEC6524}{49}{B0}" alignment="left" quickStyleIndex="0"><one:T style="font-family:Calibri;font-size:11.0pt"><![CDATA[<span |
| 5 | style='font-style:italic'>Child</span>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-08T19:25:55.000Z" lastModifiedTime="2026-09-08T19:25:55.000Z" objectID="{BB58FF99-1D15-45F6-85D0-E76F5CEC6524}{55}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Grandchild]]></one:T></one:OE></one:OEChildren></one:OE></one:OEChildren></one:OE><one:OE creationTime="2026-09-08T19:25:55.000Z" lastModifiedTime="2026-09-08T19:25:55.000Z" objectID="{BB58FF99-1D15-45F6-85D0-E76F5CEC6524}{60}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Trailing sibling]]></one:T></one:OE></one:OEChildren></one:Outline><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T19:25:57.000Z" objectID="{BB58FF99-1D15-45F6-85D0-E76F5CEC6524}{65}{B0}"><one:Position x="540.0" y="108.0" z="1"/><one:Size width="99.85796356201171" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-08T19:25:55.000Z" lastModifiedTime="2026-09-08T19:25:55.000Z" objectID="{BB58FF99-1D15-45F6-85D0-E76F5CEC6524}{66}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Other outline]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/table-edit/nested/cold/read/page-009.xml+3-3| ... | @@ -1,5 +1,5 @@ | ... | @@ -1,5 +1,5 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{E63E6C84-AAC4-4272-82DB-0820DBFF5E89}{1}{B0}" name="Delete numbered subtree" dateTime="2026-09-08T19:25:57.000Z" lastModifiedTime="2026-09-12T21:55:19.000Z" pageLevel="1" lang="en-US"><one:TagDef index="0" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Preserved task"/><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{D89C2513-8081-4B38-ADDB-D4796BE57B6D}{33}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Delete numbered subtree]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T19:26:01.000Z" objectID="{D89C2513-8081-4B38-ADDB-D4796BE57B6D}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="142.6107788085937" height="67.13858032226562"/><one:OEChildren><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{D89C2513-8081-4B38-ADDB-D4796BE57B6D}{40}{B0}" alignment="left" quickStyleIndex="0"><one:List><one:Number numberSequence="1" numberFormat="##." fontSize="11.0" font="Calibri" text="I."/></one:List><one:T><![CDATA[Anchor]]></one:T></one:OE><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{D89C2513-8081-4B38-ADDB-D4796BE57B6D}{46}{B0}" alignment="left" quickStyleIndex="0"><one:Tag index="0" completed="true" disabled="false" creationDate="2020-01-02T03:04:05.000Z" completionDate="2020-01-02T04:05:06.000Z"/><one:List><one:Number numberSequence="1" numberFormat="##." fontSize="11.0" font="Calibri" text="II."/></one:List><one:T><![CDATA[<span | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{CDA14D30-A5B6-41D8-9FAE-3E475C8D9568}{1}{B0}" name="Outdent child across indentation gap" dateTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-26T06:39:46.000Z" pageLevel="1" lang="en-US"><one:TagDef index="0" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Preserved task"/><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{E8916D2F-C48A-472F-A283-A431DC7A5756}{33}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Outdent child across indentation gap]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T19:26:01.000Z" objectID="{E8916D2F-C48A-472F-A283-A431DC7A5756}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="196.6107788085937" height="80.56629943847656"/><one:OEChildren><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{E8916D2F-C48A-472F-A283-A431DC7A5756}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Anchor]]></one:T><one:OEChildren indent="2"><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{E8916D2F-C48A-472F-A283-A431DC7A5756}{45}{B0}" alignment="left" quickStyleIndex="0"><one:Tag index="0" completed="true" disabled="false" creationDate="2020-01-02T03:04:05.000Z" completionDate="2020-01-02T04:05:06.000Z"/><one:T><![CDATA[<span |
| 3 | style='font-weight:bold' lang=en-US>Target &#129408; é</span><span lang=en-US> </span><a | 3 | style='font-weight:bold' lang=en-US>Target &#129408; é</span><span lang=en-US> </span><a |
| 4 | href="https://example.invalid/tree"><span lang=x-none>Link label</span></a>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{D89C2513-8081-4B38-ADDB-D4796BE57B6D}{55}{B0}" alignment="left" quickStyleIndex="0"><one:T style="font-family:Calibri;font-size:11.0pt"><![CDATA[<span | 4 | href="https://example.invalid/tree"><span lang=x-none>Link label</span></a>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{E8916D2F-C48A-472F-A283-A431DC7A5756}{54}{B0}" alignment="left" quickStyleIndex="0"><one:T style="font-family:Calibri;font-size:11.0pt"><![CDATA[<span |
| 5 | style='font-style:italic'>Child</span>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{D89C2513-8081-4B38-ADDB-D4796BE57B6D}{61}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Grandchild]]></one:T></one:OE></one:OEChildren></one:OE></one:OEChildren></one:OE><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{D89C2513-8081-4B38-ADDB-D4796BE57B6D}{67}{B0}" alignment="left" quickStyleIndex="0"><one:List><one:Number numberSequence="1" numberFormat="##." fontSize="11.0" font="Calibri" text="III."/></one:List><one:T><![CDATA[Trailing sibling]]></one:T></one:OE></one:OEChildren></one:Outline><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T19:26:01.000Z" objectID="{D89C2513-8081-4B38-ADDB-D4796BE57B6D}{73}{B0}"><one:Position x="540.0" y="108.0" z="1"/><one:Size width="99.85796356201171" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{D89C2513-8081-4B38-ADDB-D4796BE57B6D}{74}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Other outline]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> | 5 | style='font-style:italic'>Child</span>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{E8916D2F-C48A-472F-A283-A431DC7A5756}{60}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Grandchild]]></one:T></one:OE></one:OEChildren></one:OE></one:OEChildren></one:OE><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{E8916D2F-C48A-472F-A283-A431DC7A5756}{65}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Other child]]></one:T></one:OE></one:OEChildren></one:OE><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{E8916D2F-C48A-472F-A283-A431DC7A5756}{70}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Trailing sibling]]></one:T></one:OE></one:OEChildren></one:Outline><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T19:26:01.000Z" objectID="{E8916D2F-C48A-472F-A283-A431DC7A5756}{76}{B0}"><one:Position x="540.0" y="108.0" z="1"/><one:Size width="99.85796356201171" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{E8916D2F-C48A-472F-A283-A431DC7A5756}{77}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Other outline]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/table-edit/nested/cold/read/page-010.xml+4-19| ... | @@ -1,20 +1,5 @@ | ... | @@ -1,20 +1,5 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{F0EEDF5D-F91B-47BB-B831-3A297A91E61E}{1}{B0}" name="Bold 🦀 italic é color 東京" dateTime="2026-09-05T05:06:54.000Z" lastModifiedTime="2026-09-12T21:55:19.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Outline author="Rich author" lastModifiedBy="Rich author" lastModifiedTime="2026-09-08T04:47:24.000Z" objectID="{972277E1-B26E-440D-B7C3-B6F2850D037F}{10}{B0}"><one:Position x="36.0" y="14.40000057220459" z="0"/><one:Size width="127.5136947631836" height="42.90299224853515"/><one:OEChildren><one:OE creationTime="2026-09-08T04:47:24.000Z" lastModifiedTime="2026-09-08T04:47:24.000Z" objectID="{9047DF16-4113-0F07-0797-75F84E667189}{1}{B0}" alignment="left"><one:T><![CDATA[<span style='font-weight:bold; | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{D2520685-CF8A-4B44-8441-FE9760B36862}{1}{B0}" name="Move numbered subtree down" dateTime="2026-09-08T19:25:57.000Z" lastModifiedTime="2026-09-26T06:39:46.000Z" pageLevel="1" lang="en-US"><one:TagDef index="0" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Preserved task"/><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-08T19:25:57.000Z" lastModifiedTime="2026-09-08T19:25:57.000Z" objectID="{B869E296-CECB-4F27-A12E-3F5BD66F6DB3}{33}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Move numbered subtree down]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T19:26:01.000Z" objectID="{B869E296-CECB-4F27-A12E-3F5BD66F6DB3}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="142.6107788085937" height="67.13858032226562"/><one:OEChildren><one:OE creationTime="2026-09-08T19:25:57.000Z" lastModifiedTime="2026-09-08T19:25:57.000Z" objectID="{B869E296-CECB-4F27-A12E-3F5BD66F6DB3}{40}{B0}" alignment="left" quickStyleIndex="0"><one:List><one:Number numberSequence="1" numberFormat="##." fontSize="11.0" font="Calibri" text="I."/></one:List><one:T><![CDATA[Anchor]]></one:T></one:OE><one:OE creationTime="2026-09-08T19:25:57.000Z" lastModifiedTime="2026-09-08T19:25:57.000Z" objectID="{B869E296-CECB-4F27-A12E-3F5BD66F6DB3}{46}{B0}" alignment="left" quickStyleIndex="0"><one:Tag index="0" completed="true" disabled="false" creationDate="2020-01-02T03:04:05.000Z" completionDate="2020-01-02T04:05:06.000Z"/><one:List><one:Number numberSequence="1" numberFormat="##." fontSize="11.0" font="Calibri" text="II."/></one:List><one:T><![CDATA[<span |
| 3 | text-decoration:underline;font-family:Calibri;font-size:11.0pt'>Bold</span><span | 3 | style='font-weight:bold' lang=en-US>Target &#129408; é</span><span lang=en-US> </span><a |
| 4 | style='font-family:Calibri;font-size:11.0pt'> </span><span style='font-family: | 4 | href="https://example.invalid/tree"><span lang=x-none>Link label</span></a>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-08T19:25:57.000Z" lastModifiedTime="2026-09-08T19:25:57.000Z" objectID="{B869E296-CECB-4F27-A12E-3F5BD66F6DB3}{55}{B0}" alignment="left" quickStyleIndex="0"><one:T style="font-family:Calibri;font-size:11.0pt"><![CDATA[<span |
| 5 | Arial;font-size:13.5pt'>&#129408;</span><span style='font-family:Calibri; | 5 | style='font-style:italic'>Child</span>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-08T19:25:57.000Z" lastModifiedTime="2026-09-08T19:25:57.000Z" objectID="{B869E296-CECB-4F27-A12E-3F5BD66F6DB3}{61}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Grandchild]]></one:T></one:OE></one:OEChildren></one:OE></one:OEChildren></one:OE><one:OE creationTime="2026-09-08T19:25:57.000Z" lastModifiedTime="2026-09-08T19:25:57.000Z" objectID="{B869E296-CECB-4F27-A12E-3F5BD66F6DB3}{67}{B0}" alignment="left" quickStyleIndex="0"><one:List><one:Number numberSequence="1" numberFormat="##." fontSize="11.0" font="Calibri" text="III."/></one:List><one:T><![CDATA[Trailing sibling]]></one:T></one:OE></one:OEChildren></one:Outline><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T19:26:01.000Z" objectID="{B869E296-CECB-4F27-A12E-3F5BD66F6DB3}{73}{B0}"><one:Position x="540.0" y="108.0" z="1"/><one:Size width="99.85796356201171" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-08T19:25:57.000Z" lastModifiedTime="2026-09-08T19:25:57.000Z" objectID="{B869E296-CECB-4F27-A12E-3F5BD66F6DB3}{74}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Other outline]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
| 6 | font-size:11.0pt'> </span><span style='font-style:italic;text-decoration:line-through; | ||
| 7 | font-family:Calibri;font-size:11.0pt'>italic</span><span style='font-family: | ||
| 8 | Calibri;font-size:11.0pt'> </span><span style='vertical-align:super;font-family: | ||
| 9 | Calibri;font-size:11.0pt'>é</span><span style='font-family:Calibri;font-size: | ||
| 10 | 11.0pt'> </span><span style='font-family:Calibri;font-size:11.0pt;color:#0C2238; | ||
| 11 | background:yellow;mso-highlight:yellow'>color</span><span style='font-family: | ||
| 12 | Calibri;font-size:11.0pt'> </span><span style='vertical-align:sub;font-family: | ||
| 13 | Calibri;font-size:11.0pt'>東京</span><span style='font-family:Calibri;font-size: | ||
| 14 | 11.0pt'><br> | ||
| 15 | End</span>]]></one:T></one:OE><one:OE author="snow" authorInitials="S" lastModifiedBy="snow" lastModifiedByInitials="S" creationTime="2026-09-05T05:06:55.000Z" lastModifiedTime="2026-09-05T05:06:56.000Z" objectID="{972277E1-B26E-440D-B7C3-B6F2850D037F}{11}{B0}" alignment="left" quickStyleIndex="0" style="font-size:11.0pt;color:#1F4E79"><one:T><![CDATA[<span | ||
| 16 | style='font-weight:bold;font-family:Calibri' lang=en-US>Fictitious: café, </span><span | ||
| 17 | style='font-weight:bold;font-family:SimSun' lang=en-US>東京</span><span | ||
| 18 | style='font-weight:bold;font-family:Calibri' lang=en-US>, </span><span | ||
| 19 | style='font-weight:bold;font-family:Arial;direction:rtl;unicode-bidi:embed' | ||
| 20 | lang=ar-SA>مرحبا</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/table-edit/nested/cold/read/page-011.xml+3-2| ... | @@ -1,4 +1,5 @@ | ... | @@ -1,4 +1,5 @@ |
| 1 | <?xml version="1.0"?> | 1 | <?xml version="1.0"?> |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{F0F484B0-2100-4784-B9E5-4A8B81EF98E2}{1}{B0}" name="Delete unindented sibling after group" dateTime="2026-09-08T19:25:55.000Z" lastModifiedTime="2026-09-12T21:55:19.000Z" pageLevel="1" lang="en-US"><one:TagDef index="0" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Preserved task"/><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-08T19:25:56.000Z" lastModifiedTime="2026-09-08T19:25:56.000Z" objectID="{81A62340-0F2E-42B2-9749-B51DDC107DBD}{33}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Delete unindented sibling after group]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T19:25:57.000Z" objectID="{81A62340-0F2E-42B2-9749-B51DDC107DBD}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="129.6621246337891" height="26.85543441772461"/><one:OEChildren indent="2"><one:OE creationTime="2026-09-08T19:25:56.000Z" lastModifiedTime="2026-09-08T19:25:56.000Z" objectID="{81A62340-0F2E-42B2-9749-B51DDC107DBD}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Anchor]]></one:T></one:OE></one:OEChildren><one:OEChildren><one:OE creationTime="2026-09-08T19:25:56.000Z" lastModifiedTime="2026-09-08T19:25:56.000Z" objectID="{81A62340-0F2E-42B2-9749-B51DDC107DBD}{45}{B0}" alignment="left" quickStyleIndex="0"><one:Tag index="0" completed="true" disabled="false" creationDate="2020-01-02T03:04:05.000Z" completionDate="2020-01-02T04:05:06.000Z"/><one:T><![CDATA[<span | 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{E3DFE2CC-8F80-4980-91CA-5EE3D0C87070}{1}{B0}" name="Delete cell subtree" dateTime="2026-09-08T19:25:56.000Z" lastModifiedTime="2026-09-26T06:39:46.000Z" pageLevel="1" lang="en-US"><one:TagDef index="0" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Preserved task"/><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-08T19:25:56.000Z" lastModifiedTime="2026-09-08T19:25:56.000Z" objectID="{7DEF2CFB-0A8B-471B-9FB7-68FA08F0F135}{33}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Delete cell subtree]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-26T06:32:49.000Z" objectID="{7DEF2CFB-0A8B-471B-9FB7-68FA08F0F135}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="428.1300048828125" height="62.30315780639648"/><one:OEChildren><one:OE creationTime="2026-09-08T19:25:56.000Z" lastModifiedTime="2026-09-26T06:32:49.000Z" objectID="{7DEF2CFB-0A8B-471B-9FB7-68FA08F0F135}{40}{B0}" alignment="left"><one:Table bordersVisible="true" lastModifiedTime="2026-09-26T06:32:49.000Z" objectID="{7DEF2CFB-0A8B-471B-9FB7-68FA08F0F135}{41}{B0}"><one:Columns><one:Column index="0" width="240.0" isLocked="true"/><one:Column index="1" width="180.0" isLocked="true"/></one:Columns><one:Row objectID="{7DEF2CFB-0A8B-471B-9FB7-68FA08F0F135}{42}{B0}" lastModifiedTime="2026-09-26T06:32:49.000Z"><one:Cell lastModifiedTime="2026-09-08T19:25:57.000Z" objectID="{7DEF2CFB-0A8B-471B-9FB7-68FA08F0F135}{43}{B0}" lastModifiedByInitials="V"><one:OEChildren><one:OE creationTime="2026-09-08T19:25:56.000Z" lastModifiedTime="2026-09-08T19:25:56.000Z" objectID="{7DEF2CFB-0A8B-471B-9FB7-68FA08F0F135}{46}{B0}" alignment="left" quickStyleIndex="0"><one:Tag index="0" completed="true" disabled="false" creationDate="2020-01-02T03:04:05.000Z" completionDate="2020-01-02T04:05:06.000Z"/><one:T><![CDATA[<span |
| 3 | style='font-weight:bold' lang=en-US>Target &#129408; é</span><span lang=en-US> </span><a | 3 | style='font-weight:bold' lang=en-US>Target &#129408; é</span><span lang=en-US> </span><a |
| 4 | href="https://example.invalid/tree"><span lang=x-none>Link label</span></a>]]></one:T></one:OE></one:OEChildren></one:Outline><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T19:25:57.000Z" objectID="{81A62340-0F2E-42B2-9749-B51DDC107DBD}{55}{B0}"><one:Position x="540.0" y="108.0" z="1"/><one:Size width="99.85796356201171" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-08T19:25:56.000Z" lastModifiedTime="2026-09-08T19:25:56.000Z" objectID="{81A62340-0F2E-42B2-9749-B51DDC107DBD}{56}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Other outline]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> | 4 | href="https://example.invalid/tree"><span lang=x-none>Link label</span></a>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-08T19:25:56.000Z" lastModifiedTime="2026-09-08T19:25:56.000Z" objectID="{7DEF2CFB-0A8B-471B-9FB7-68FA08F0F135}{55}{B0}" alignment="left" quickStyleIndex="0"><one:T style="font-family:Calibri;font-size:11.0pt"><![CDATA[<span |
| 5 | style='font-style:italic'>Child</span>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-08T19:25:56.000Z" lastModifiedTime="2026-09-08T19:25:56.000Z" objectID="{7DEF2CFB-0A8B-471B-9FB7-68FA08F0F135}{61}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Grandchild]]></one:T></one:OE></one:OEChildren></one:OE></one:OEChildren></one:OE><one:OE creationTime="2026-09-08T19:25:56.000Z" lastModifiedTime="2026-09-08T19:25:56.000Z" objectID="{7DEF2CFB-0A8B-471B-9FB7-68FA08F0F135}{66}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Cell tail]]></one:T></one:OE></one:OEChildren></one:Cell><one:Cell lastModifiedTime="2026-09-26T06:32:49.000Z" objectID="{7DEF2CFB-0A8B-471B-9FB7-68FA08F0F135}{71}{B0}" lastModifiedByInitials="V"><one:OEChildren><one:OE creationTime="2026-09-08T19:25:56.000Z" lastModifiedTime="2026-09-08T19:25:56.000Z" objectID="{7DEF2CFB-0A8B-471B-9FB7-68FA08F0F135}{74}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Other cell]]></one:T></one:OE><one:OE author="Table author" lastModifiedBy="Table author" creationTime="2026-09-26T06:32:49.000Z" lastModifiedTime="2026-09-26T06:32:49.000Z" objectID="{D749AEBE-E6B0-0495-0344-3BA186216B7C}{10}{B0}" alignment="left" style="font-family:Calibri;font-size:11.0pt"><one:Table bordersVisible="true" lastModifiedTime="2026-09-26T06:32:49.000Z" objectID="{D749AEBE-E6B0-0495-0344-3BA186216B7C}{1}{B0}"><one:Columns><one:Column index="0" width="72.0" isLocked="true"/></one:Columns><one:Row objectID="{D749AEBE-E6B0-0495-0344-3BA186216B7C}{2}{B0}" lastModifiedTime="2026-09-26T06:32:49.000Z"><one:Cell lastModifiedTime="2026-09-26T06:32:49.000Z" objectID="{D749AEBE-E6B0-0495-0344-3BA186216B7C}{3}{B0}"><one:OEChildren><one:OE creationTime="2026-09-26T06:32:49.000Z" lastModifiedTime="2026-09-26T06:32:49.000Z" objectID="{D749AEBE-E6B0-0495-0344-3BA186216B7C}{4}{B0}" alignment="left"><one:T><![CDATA[Inner one]]></one:T></one:OE></one:OEChildren></one:Cell></one:Row><one:Row objectID="{D749AEBE-E6B0-0495-0344-3BA186216B7C}{6}{B0}" lastModifiedTime="2026-09-26T06:32:49.000Z"><one:Cell lastModifiedTime="2026-09-26T06:32:49.000Z" objectID="{D749AEBE-E6B0-0495-0344-3BA186216B7C}{7}{B0}"><one:OEChildren><one:OE creationTime="2026-09-26T06:32:49.000Z" lastModifiedTime="2026-09-26T06:32:49.000Z" objectID="{D749AEBE-E6B0-0495-0344-3BA186216B7C}{8}{B0}" alignment="left"><one:T><![CDATA[Inner two]]></one:T></one:OE></one:OEChildren></one:Cell></one:Row></one:Table></one:OE></one:OEChildren></one:Cell></one:Row></one:Table></one:OE></one:OEChildren></one:Outline><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T19:25:57.000Z" objectID="{7DEF2CFB-0A8B-471B-9FB7-68FA08F0F135}{79}{B0}"><one:Position x="540.0" y="108.0" z="1"/><one:Size width="99.85796356201171" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-08T19:25:56.000Z" lastModifiedTime="2026-09-08T19:25:56.000Z" objectID="{7DEF2CFB-0A8B-471B-9FB7-68FA08F0F135}{80}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Other outline]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/table-edit/nested/cold/run.json+1-1| ... | @@ -1,5 +1,5 @@ | ... | @@ -1,5 +1,5 @@ |
| 1 | { | 1 | { |
| 2 | "notebook": "/private/tmp/m4/nested", | 2 | "notebook": "/Users/clo/dev/one/evidence/titles/table-nested", |
| 3 | "expected_pages": 12, | 3 | "expected_pages": 12, |
| 4 | "author": null, | 4 | "author": null, |
| 5 | "author_timeout_seconds": 600, | 5 | "author_timeout_seconds": 600, |
corpus/table-edit/nested/cold/source.json+5-5| ... | @@ -2,13 +2,13 @@ | ... | @@ -2,13 +2,13 @@ |
| 2 | { | 2 | { |
| 3 | "path": "Open Notebook.onetoc2", | 3 | "path": "Open Notebook.onetoc2", |
| 4 | "bytes": 1612, | 4 | "bytes": 1612, |
| 5 | "sha256": "1c7c0e30cc2c2f029f87e20bba0981f0bf8a7f2f6643ad67a24e80834f539991", | 5 | "sha256": "1290deaddfd9b3dc7c08ee83da786bc057c7e18ec6a9212b0dc8453a7254c09b", |
| 6 | "mtime_ns": 1789250020753460025 | 6 | "mtime_ns": 1790404369595025987 |
| 7 | }, | 7 | }, |
| 8 | { | 8 | { |
| 9 | "path": "synthetic.one", | 9 | "path": "synthetic.one", |
| 10 | "bytes": 91192, | 10 | "bytes": 90608, |
| 11 | "sha256": "6f3b8737c318834e2958368ed9fd9bc2ff4c7490bbd4a29907fa76e472355b43", | 11 | "sha256": "2d0333a99c6cdf9499ebf972fb53a6cf987ceba1950738f01158e028e8b82fc1", |
| 12 | "mtime_ns": 1789250020752551397 | 12 | "mtime_ns": 1790404369594093530 |
| 13 | } | 13 | } |
| 14 | ] | 14 | ] |
crates/notebook/README.md+8-2| ... | @@ -188,6 +188,9 @@ object space, which survives the tested native compaction that replaces the file | ... | @@ -188,6 +188,9 @@ object space, which survives the tested native compaction that replaces the file |
| 188 | An `Arc<Replica>` can own one background worker. Supply a connection factory, poll | 188 | An `Arc<Replica>` can own one background worker. Supply a connection factory, poll |
| 189 | interval and observer; successful publications drain immediately, durable local | 189 | interval and observer; successful publications drain immediately, durable local |
| 190 | edits wake the worker, and `wake()` requests an immediate reachability retry. | 190 | edits wake the worker, and `wake()` requests an immediate reachability retry. |
| 191 | While nothing is queued, a remote whose `Remote::stamp` (its header and length, read | ||
| 192 | without coordination) equals the last observed image's is not read again; publishing | ||
| 193 | against an unchanged stamp needs no read either. | ||
| 191 | Transport failures discard the old connection and retry through the factory; | 194 | Transport failures discard the old connection and retry through the factory; |
| 192 | Read contention and `NotCommitted` operations with `WouldBlock` or `ResourceBusy` | 195 | Read contention and `NotCommitted` operations with `WouldBlock` or `ResourceBusy` |
| 193 | reuse the connection. Contended `NotCommitted` operations use randomized backoff, | 196 | reuse the connection. Contended `NotCommitted` operations use randomized backoff, |
| ... | @@ -383,6 +386,7 @@ experimental Rust API with native interoperability evidence in the repository's | ... | @@ -383,6 +386,7 @@ experimental Rust API with native interoperability evidence in the repository's |
| 383 | [Milestone 9](../../evidence/MILESTONE9.md). | 386 | [Milestone 9](../../evidence/MILESTONE9.md). |
| 384 | 387 | ||
| 385 | ```no_run | 388 | ```no_run |
| 389 | # #[cfg(feature = "smb")] { | ||
| 386 | use notebook::smb::{Client, Credentials}; | 390 | use notebook::smb::{Client, Credentials}; |
| 387 | use std::time::Duration; | 391 | use std::time::Duration; |
| 388 | 392 | ||
| ... | @@ -396,6 +400,7 @@ let snapshot = client.read("Personal/Video.one", 64 * 1024 * 1024)?; | ... | @@ -396,6 +400,7 @@ let snapshot = client.read("Personal/Video.one", 64 * 1024 * 1024)?; |
| 396 | let store = onestore::Store::parse(&snapshot)?; | 400 | let store = onestore::Store::parse(&snapshot)?; |
| 397 | let revisions = onestore::RevisionIndex::parse(&store)?; | 401 | let revisions = onestore::RevisionIndex::parse(&store)?; |
| 398 | let document = onestore::document::Document::parse(&revisions)?; | 402 | let document = onestore::document::Document::parse(&revisions)?; |
| 403 | # } | ||
| 399 | # Ok::<(), Box<dyn std::error::Error>>(()) | 404 | # Ok::<(), Box<dyn std::error::Error>>(()) |
| 400 | ``` | 405 | ``` |
| 401 | 406 | ||
| ... | @@ -404,8 +409,9 @@ snapshot. Call from a background thread outside a Tokio runtime. Use identities | ... | @@ -404,8 +409,9 @@ snapshot. Call from a background thread outside a Tokio runtime. Use identities |
| 404 | from the document and the same snapshot with `Client::commit_text` or | 409 | from the document and the same snapshot with `Client::commit_text` or |
| 405 | `Client::commit_property_bytes`; their errors retain `onestore::CommitState`. | 410 | `Client::commit_property_bytes`; their errors retain `onestore::CommitState`. |
| 406 | `PreparedEdit::page` separates preparation from I/O: inspect the immutable image | 411 | `PreparedEdit::page` separates preparation from I/O: inspect the immutable image |
| 407 | and persist the intended revision identity before `Client::commit_prepared`. | 412 | and persist the intended revision identity before `Client::commit_transaction` publishes |
| 408 | `Client::confirm_snapshot` compares and flushes an observed image, then refreshes | 413 | its `transaction()`. `Client::stamp` reads a file's header and length without coordination, |
| 414 | for polling. `Client::confirm_snapshot` checks and flushes an observed image's stamp, then refreshes | ||
| 409 | its header version metadata without adding a revision. The caller must first | 415 | its header version metadata without adding a revision. The caller must first |
| 410 | establish which intents that image contains and reread before another commit. | 416 | establish which intents that image contains and reread before another commit. |
| 411 | 417 |
crates/notebook/examples/queue_scale.rs+3-3| ... | @@ -1,6 +1,6 @@ | ... | @@ -1,6 +1,6 @@ |
| 1 | use notebook::{EditStatus, Remote, Replica}; | 1 | use notebook::{EditStatus, Remote, Replica}; |
| 2 | use onestore::{ | 2 | use onestore::{ |
| 3 | CommitError, PageCreation, PreparedEdit, RevisionIndex, Store, | 3 | CommitError, PageCreation, PreparedEdit, RevisionIndex, Store, Transaction, |
| 4 | document::Document, | 4 | document::Document, |
| 5 | page::{Page, PageObject, Paragraph, text::Edit}, | 5 | page::{Page, PageObject, Paragraph, text::Edit}, |
| 6 | }; | 6 | }; |
| ... | @@ -12,8 +12,8 @@ impl Remote for FileRemote { | ... | @@ -12,8 +12,8 @@ impl Remote for FileRemote { |
| 12 | fn read(&mut self) -> io::Result<Vec<u8>> { | 12 | fn read(&mut self) -> io::Result<Vec<u8>> { |
| 13 | onestore::read_file(&self.0) | 13 | onestore::read_file(&self.0) |
| 14 | } | 14 | } |
| 15 | fn publish(&mut self, edit: &PreparedEdit<'_>) -> Result<(), CommitError> { | 15 | fn publish(&mut self, transaction: &Transaction) -> Result<(), CommitError> { |
| 16 | edit.commit_file(&self.0) | 16 | transaction.commit_file(&self.0) |
| 17 | } | 17 | } |
| 18 | fn confirm(&mut self, snapshot: &[u8]) -> Result<(), CommitError> { | 18 | fn confirm(&mut self, snapshot: &[u8]) -> Result<(), CommitError> { |
| 19 | onestore::confirm_file_snapshot(&self.0, snapshot) | 19 | onestore::confirm_file_snapshot(&self.0, snapshot) |
crates/notebook/examples/recovery_probe.rs+3-3| ... | @@ -4,7 +4,7 @@ mod support { | ... | @@ -4,7 +4,7 @@ mod support { |
| 4 | use support::view::view; | 4 | use support::view::view; |
| 5 | 5 | ||
| 6 | use notebook::{EditStatus, Remote, Replica}; | 6 | use notebook::{EditStatus, Remote, Replica}; |
| 7 | use onestore::{CommitError, CommitIo, PreparedEdit}; | 7 | use onestore::{CommitError, CommitIo, Transaction}; |
| 8 | use serde_json::json; | 8 | use serde_json::json; |
| 9 | use std::{ | 9 | use std::{ |
| 10 | env, | 10 | env, |
| ... | @@ -67,9 +67,9 @@ impl Remote for Disk { | ... | @@ -67,9 +67,9 @@ impl Remote for Disk { |
| 67 | phase("read-after"); | 67 | phase("read-after"); |
| 68 | result | 68 | result |
| 69 | } | 69 | } |
| 70 | fn publish(&mut self, edit: &PreparedEdit<'_>) -> Result<(), CommitError> { | 70 | fn publish(&mut self, transaction: &Transaction) -> Result<(), CommitError> { |
| 71 | phase("publish-before"); | 71 | phase("publish-before"); |
| 72 | let result = edit.commit(self); | 72 | let result = transaction.commit(self); |
| 73 | phase("publish-after"); | 73 | phase("publish-after"); |
| 74 | result | 74 | result |
| 75 | } | 75 | } |
crates/notebook/examples/smb_offline_client.rs+14-6| ... | @@ -4,7 +4,7 @@ mod concurrent; | ... | @@ -4,7 +4,7 @@ mod concurrent; |
| 4 | use notebook::smb::{Client, Credentials}; | 4 | use notebook::smb::{Client, Credentials}; |
| 5 | use notebook::{EditStatus, Error, Remote, Replica, SmbRemote}; | 5 | use notebook::{EditStatus, Error, Remote, Replica, SmbRemote}; |
| 6 | use onestore::{ | 6 | use onestore::{ |
| 7 | CommitError, CommitState, ExGuid, PreparedEdit, RevisionIndex, Store, page::Paragraph, | 7 | CommitError, CommitState, ExGuid, RevisionIndex, Store, Transaction, page::Paragraph, |
| 8 | }; | 8 | }; |
| 9 | use serde_json::json; | 9 | use serde_json::json; |
| 10 | use std::{ | 10 | use std::{ |
| ... | @@ -56,6 +56,8 @@ enum Pause { | ... | @@ -56,6 +56,8 @@ enum Pause { |
| 56 | struct Traced<R> { | 56 | struct Traced<R> { |
| 57 | remote: R, | 57 | remote: R, |
| 58 | before: Option<(String, Option<DocumentView>)>, | 58 | before: Option<(String, Option<DocumentView>)>, |
| 59 | /// The last image read, which publications apply to. | ||
| 60 | read: Vec<u8>, | ||
| 59 | pause: Option<Pause>, | 61 | pause: Option<Pause>, |
| 60 | documents: bool, | 62 | documents: bool, |
| 61 | } | 63 | } |
| ... | @@ -75,15 +77,21 @@ impl<R: Remote> Remote for Traced<R> { | ... | @@ -75,15 +77,21 @@ impl<R: Remote> Remote for Traced<R> { |
| 75 | json!({"event":"read", "started_us":started, "finished_us":now(), "text":observed.text, "documents":documents.as_ref().map(|view| &view.texts), "document_graph":documents.as_ref().map(|view| &view.graph)}) | 77 | json!({"event":"read", "started_us":started, "finished_us":now(), "text":observed.text, "documents":documents.as_ref().map(|view| &view.texts), "document_graph":documents.as_ref().map(|view| &view.graph)}) |
| 76 | ); | 78 | ); |
| 77 | self.before = Some((observed.text, documents)); | 79 | self.before = Some((observed.text, documents)); |
| 80 | self.read.clone_from(&bytes); | ||
| 78 | Ok(bytes) | 81 | Ok(bytes) |
| 79 | } | 82 | } |
| 80 | fn publish(&mut self, edit: &PreparedEdit<'_>) -> Result<(), CommitError> { | 83 | fn publish(&mut self, transaction: &Transaction) -> Result<(), CommitError> { |
| 81 | let after = view(edit.as_bytes()).map_err(|error| CommitError { | 84 | let mut image = self.read.clone(); |
| 85 | transaction.apply(&mut image).map_err(|error| CommitError { | ||
| 86 | state: CommitState::NotCommitted, | ||
| 87 | error: io::Error::other(error.to_string()), | ||
| 88 | })?; | ||
| 89 | let after = view(&image).map_err(|error| CommitError { | ||
| 82 | state: CommitState::NotCommitted, | 90 | state: CommitState::NotCommitted, |
| 83 | error: io::Error::other(error.to_string()), | 91 | error: io::Error::other(error.to_string()), |
| 84 | })?; | 92 | })?; |
| 85 | let documents = if self.documents { | 93 | let documents = if self.documents { |
| 86 | Some(document_view(edit.as_bytes()).map_err(|error| CommitError { | 94 | Some(document_view(&image).map_err(|error| CommitError { |
| 87 | state: CommitState::NotCommitted, | 95 | state: CommitState::NotCommitted, |
| 88 | error: io::Error::other(error), | 96 | error: io::Error::other(error), |
| 89 | })?) | 97 | })?) |
| ... | @@ -151,7 +159,7 @@ impl<R: Remote> Remote for Traced<R> { | ... | @@ -151,7 +159,7 @@ impl<R: Remote> Remote for Traced<R> { |
| 151 | } | 159 | } |
| 152 | } | 160 | } |
| 153 | let started = now(); | 161 | let started = now(); |
| 154 | let result = self.remote.publish(edit); | 162 | let result = self.remote.publish(transaction); |
| 155 | let finished = now(); | 163 | let finished = now(); |
| 156 | println!( | 164 | println!( |
| 157 | "{}", | 165 | "{}", |
| ... | @@ -399,7 +407,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> { | ... | @@ -399,7 +407,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> { |
| 399 | let worker = cache.start_sync(Duration::from_millis(50), move || { | 407 | let worker = cache.start_sync(Duration::from_millis(50), move || { |
| 400 | let client = Client::connect(&address, &share, Credentials::default(), Duration::from_secs(5))?; | 408 | let client = Client::connect(&address, &share, Credentials::default(), Duration::from_secs(5))?; |
| 401 | println!("{}", json!({"event":"transport_connected", "at_us":now()})); | 409 | println!("{}", json!({"event":"transport_connected", "at_us":now()})); |
| 402 | Ok(Traced { remote: SmbRemote::new(client, &path, 256 * 1024 * 1024), before:None, pause:pause.clone(), documents }) | 410 | Ok(Traced { remote: SmbRemote::new(client, &path, 256 * 1024 * 1024), before:None, read:Vec::new(), pause:pause.clone(), documents }) |
| 403 | }, move |result| { | 411 | }, move |result| { |
| 404 | if let Err(error) = result { | 412 | if let Err(error) = result { |
| 405 | println!("{}", json!({"event":"sync_error", "error":error.to_string(), "at_us":now()})); | 413 | println!("{}", json!({"event":"sync_error", "error":error.to_string(), "at_us":now()})); |
crates/notebook/src/images.rs+29-2| ... | @@ -3,6 +3,7 @@ | ... | @@ -3,6 +3,7 @@ |
| 3 | //! instead of the section. | 3 | //! instead of the section. |
| 4 | 4 | ||
| 5 | use crate::Result; | 5 | use crate::Result; |
| 6 | use onestore::Stamp; | ||
| 6 | use rusqlite::{Connection, params}; | 7 | use rusqlite::{Connection, params}; |
| 7 | use std::io; | 8 | use std::io; |
| 8 | 9 | ||
| ... | @@ -91,8 +92,9 @@ pub(crate) fn working_length(connection: &Connection) -> Result<u64> { | ... | @@ -91,8 +92,9 @@ pub(crate) fn working_length(connection: &Connection) -> Result<u64> { |
| 91 | "SELECT length(base), substr(working, 1, 8) FROM replica WHERE id=1", | 92 | "SELECT length(base), substr(working, 1, 8) FROM replica WHERE id=1", |
| 92 | [], | 93 | [], |
| 93 | |row| { | 94 | |row| { |
| 94 | let header: Vec<u8> = row.get(1)?; | 95 | // SQLite's substr of an empty blob is NULL. |
| 95 | Ok(match header.first_chunk::<8>() { | 96 | let header: Option<Vec<u8>> = row.get(1)?; |
| 97 | Ok(match header.as_deref().and_then(<[u8]>::first_chunk::<8>) { | ||
| 96 | Some(length) => u64::from_le_bytes(*length), | 98 | Some(length) => u64::from_le_bytes(*length), |
| 97 | None => row.get::<_, i64>(0)? as u64, | 99 | None => row.get::<_, i64>(0)? as u64, |
| 98 | }) | 100 | }) |
| ... | @@ -100,6 +102,31 @@ pub(crate) fn working_length(connection: &Connection) -> Result<u64> { | ... | @@ -100,6 +102,31 @@ pub(crate) fn working_length(connection: &Connection) -> Result<u64> { |
| 100 | )?) | 102 | )?) |
| 101 | } | 103 | } |
| 102 | 104 | ||
| 105 | /// Whether no edit is queued and the working image is the base image, without reading | ||
| 106 | /// either image. | ||
| 107 | pub(crate) fn settled(connection: &Connection) -> Result<bool> { | ||
| 108 | Ok(connection.query_row( | ||
| 109 | "SELECT length(working) = 0 AND NOT EXISTS (SELECT 1 FROM edits) FROM replica WHERE id=1", | ||
| 110 | [], | ||
| 111 | |row| row.get(0), | ||
| 112 | )?) | ||
| 113 | } | ||
| 114 | |||
| 115 | /// The base image's stamp, without reading the image. | ||
| 116 | pub(crate) fn stamp(connection: &Connection) -> Result<Stamp> { | ||
| 117 | let (header, length): (Vec<u8>, i64) = connection.query_row( | ||
| 118 | "SELECT substr(base, 1, 1024), length(base) FROM replica WHERE id=1", | ||
| 119 | [], | ||
| 120 | |row| Ok((row.get(0)?, row.get(1)?)), | ||
| 121 | )?; | ||
| 122 | Ok(Stamp { | ||
| 123 | header: header | ||
| 124 | .try_into() | ||
| 125 | .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "Damaged base image"))?, | ||
| 126 | length: u64::try_from(length).map_err(io::Error::other)?, | ||
| 127 | }) | ||
| 128 | } | ||
| 129 | |||
| 103 | pub(crate) fn working(connection: &Connection) -> Result<Vec<u8>> { | 130 | pub(crate) fn working(connection: &Connection) -> Result<Vec<u8>> { |
| 104 | let (base, patch): (Vec<u8>, Vec<u8>) = | 131 | let (base, patch): (Vec<u8>, Vec<u8>) = |
| 105 | connection.query_row("SELECT base, working FROM replica WHERE id=1", [], |row| { | 132 | connection.query_row("SELECT base, working FROM replica WHERE id=1", [], |row| { |
crates/notebook/src/lib.rs+44| ... | @@ -255,6 +255,31 @@ impl Replica { | ... | @@ -255,6 +255,31 @@ impl Replica { |
| 255 | images::working(&connection) | 255 | images::working(&connection) |
| 256 | } | 256 | } |
| 257 | 257 | ||
| 258 | /// The page in `space` as the working image stores it. | ||
| 259 | pub fn page(&self, space: ExGuid) -> Result<Page> { | ||
| 260 | let snapshot = self.snapshot()?; | ||
| 261 | let store = Store::parse(&snapshot)?; | ||
| 262 | let index = RevisionIndex::parse(&store)?; | ||
| 263 | Ok(Page::from_space(&Document::parse(&index)?, space)?) | ||
| 264 | } | ||
| 265 | |||
| 266 | /// Whether no edit is queued and the remote still has the last observed image's stamp, | ||
| 267 | /// reading neither image; the cache is not locked during remote I/O. | ||
| 268 | pub(crate) fn settled(&self, remote: &mut impl Remote) -> Result<bool> { | ||
| 269 | let lock = || { | ||
| 270 | self.connection | ||
| 271 | .lock() | ||
| 272 | .map_err(|_| io::Error::other("Cache owner panicked")) | ||
| 273 | }; | ||
| 274 | if !images::settled(&*lock()?)? { | ||
| 275 | return Ok(false); | ||
| 276 | } | ||
| 277 | let Some(stamp) = remote.stamp().map_err(Error::RemoteIo)? else { | ||
| 278 | return Ok(false); | ||
| 279 | }; | ||
| 280 | Ok(images::stamp(&*lock()?)? == stamp) | ||
| 281 | } | ||
| 282 | |||
| 258 | pub fn pending(&self) -> Result<Vec<PendingEdit>> { | 283 | pub fn pending(&self) -> Result<Vec<PendingEdit>> { |
| 259 | let connection = self | 284 | let connection = self |
| 260 | .connection | 285 | .connection |
| ... | @@ -263,6 +288,25 @@ impl Replica { | ... | @@ -263,6 +288,25 @@ impl Replica { |
| 263 | pending(&connection) | 288 | pending(&connection) |
| 264 | } | 289 | } |
| 265 | 290 | ||
| 291 | /// Identities and page spaces of the pending edits, oldest first, without reading their | ||
| 292 | /// operations. | ||
| 293 | pub(crate) fn queued(&self) -> Result<Vec<(u64, ExGuid)>> { | ||
| 294 | let connection = self | ||
| 295 | .connection | ||
| 296 | .lock() | ||
| 297 | .map_err(|_| io::Error::other("Cache owner panicked"))?; | ||
| 298 | let mut query = connection.prepare("SELECT id, space FROM edits ORDER BY id")?; | ||
| 299 | let mut rows = query.query([])?; | ||
| 300 | let mut edits = Vec::new(); | ||
| 301 | while let Some(row) = rows.next()? { | ||
| 302 | edits.push(( | ||
| 303 | u64::try_from(row.get::<_, i64>(0)?).map_err(io::Error::other)?, | ||
| 304 | row.get::<_, String>(1)?.parse()?, | ||
| 305 | )); | ||
| 306 | } | ||
| 307 | Ok(edits) | ||
| 308 | } | ||
| 309 | |||
| 266 | /// Durably queues an edited page model using the supplied local snapshot. | 310 | /// Durably queues an edited page model using the supplied local snapshot. |
| 267 | /// While the newest pending edit is an unattempted save of the same page, a new save | 311 | /// While the newest pending edit is an unattempted save of the same page, a new save |
| 268 | /// replaces its result instead of queueing another publication, as OneNote does | 312 | /// replaces its result instead of queueing another publication, as OneNote does |
crates/notebook/src/merge.rs+16| ... | @@ -257,6 +257,18 @@ fn merge_paragraphs( | ... | @@ -257,6 +257,18 @@ fn merge_paragraphs( |
| 257 | } | 257 | } |
| 258 | // Child order per container: the remote order with our repositioned paragraphs re-placed. | 258 | // Child order per container: the remote order with our repositioned paragraphs re-placed. |
| 259 | let (bc, oc, tc) = (children(base), children(ours), children(theirs)); | 259 | let (bc, oc, tc) = (children(base), children(ours), children(theirs)); |
| 260 | // A paragraph we added after one they gave new children would sit after or among those | ||
| 261 | // children; Enter splitting a paragraph carries its children below, so neither is assumed. | ||
| 262 | for pair in oc.values().flat_map(|list| list.windows(2)) { | ||
| 263 | if b.contains_key(&pair[0]) | ||
| 264 | && !b.contains_key(&pair[1]) | ||
| 265 | && tc | ||
| 266 | .get(&Some(pair[0])) | ||
| 267 | .is_some_and(|children| children.iter().any(|id| !b.contains_key(id))) | ||
| 268 | { | ||
| 269 | return None; | ||
| 270 | } | ||
| 271 | } | ||
| 260 | let mut order: BTreeMap<Option<ExGuid>, Vec<ExGuid>> = BTreeMap::new(); | 272 | let mut order: BTreeMap<Option<ExGuid>, Vec<ExGuid>> = BTreeMap::new(); |
| 261 | let containers: BTreeSet<Option<ExGuid>> = bc | 273 | let containers: BTreeSet<Option<ExGuid>> = bc |
| 262 | .keys() | 274 | .keys() |
| ... | @@ -459,6 +471,10 @@ fn merge_text( | ... | @@ -459,6 +471,10 @@ fn merge_text( |
| 459 | return None; | 471 | return None; |
| 460 | } | 472 | } |
| 461 | let mapped = crate::rebase::rebase(b, theirs.text(), start..end)?; | 473 | let mapped = crate::rebase::rebase(b, theirs.text(), start..end)?; |
| 474 | // Text they reformatted is not ours to replace, nor to move as a split would. | ||
| 475 | if start < end && theirs.slice(mapped.clone()).ok()? != base.slice(start..end).ok()? { | ||
| 476 | return None; | ||
| 477 | } | ||
| 462 | let mut merged = theirs.clone(); | 478 | let mut merged = theirs.clone(); |
| 463 | merged | 479 | merged |
| 464 | .apply(Edit { | 480 | .apply(Edit { |
crates/notebook/src/session.rs+44-18| ... | @@ -5,8 +5,8 @@ use crate::{ | ... | @@ -5,8 +5,8 @@ use crate::{ |
| 5 | ConflictKind, EditStatus, Error, PendingEdit, Remote, Replica, Result, SyncWorker, discover, | 5 | ConflictKind, EditStatus, Error, PendingEdit, Remote, Replica, Result, SyncWorker, discover, |
| 6 | }; | 6 | }; |
| 7 | use onestore::{ | 7 | use onestore::{ |
| 8 | CommitError, ExGuid, PageCreation, PreparedEdit, RevisionIndex, Store, document::Document, | 8 | CommitError, ExGuid, PageCreation, PreparedEdit, RevisionIndex, Stamp, Store, Transaction, |
| 9 | page::Page, | 9 | document::Document, page::Page, |
| 10 | }; | 10 | }; |
| 11 | use std::{ | 11 | use std::{ |
| 12 | collections::{BTreeMap, VecDeque}, | 12 | collections::{BTreeMap, VecDeque}, |
| ... | @@ -15,6 +15,7 @@ use std::{ | ... | @@ -15,6 +15,7 @@ use std::{ |
| 15 | path::{Path, PathBuf}, | 15 | path::{Path, PathBuf}, |
| 16 | sync::{ | 16 | sync::{ |
| 17 | Arc, Mutex, | 17 | Arc, Mutex, |
| 18 | atomic::{AtomicUsize, Ordering}, | ||
| 18 | mpsc::{self, Receiver, Sender}, | 19 | mpsc::{self, Receiver, Sender}, |
| 19 | }, | 20 | }, |
| 20 | thread::{self, JoinHandle}, | 21 | thread::{self, JoinHandle}, |
| ... | @@ -227,7 +228,9 @@ impl Storage for Share { | ... | @@ -227,7 +228,9 @@ impl Storage for Share { |
| 227 | } | 228 | } |
| 228 | 229 | ||
| 229 | fn commit(&self, path: &str, edit: &PreparedEdit<'_>) -> Result<()> { | 230 | fn commit(&self, path: &str, edit: &PreparedEdit<'_>) -> Result<()> { |
| 230 | Ok(self.client.commit_prepared(&self.path(path), edit)?) | 231 | Ok(self |
| 232 | .client | ||
| 233 | .commit_transaction(&self.path(path), &edit.transaction())?) | ||
| 231 | } | 234 | } |
| 232 | 235 | ||
| 233 | fn set_property( | 236 | fn set_property( |
| ... | @@ -760,6 +763,8 @@ pub struct Section { | ... | @@ -760,6 +763,8 @@ pub struct Section { |
| 760 | events: Receiver<Event>, | 763 | events: Receiver<Event>, |
| 761 | saves: Option<Sender<PageSave>>, | 764 | saves: Option<Sender<PageSave>>, |
| 762 | saved: Receiver<(ExGuid, std::result::Result<Save, String>)>, | 765 | saved: Receiver<(ExGuid, std::result::Result<Save, String>)>, |
| 766 | /// Saves queued and not yet reported. | ||
| 767 | unsaved: Arc<AtomicUsize>, | ||
| 763 | saver: Option<JoinHandle<()>>, | 768 | saver: Option<JoinHandle<()>>, |
| 764 | } | 769 | } |
| 765 | 770 | ||
| ... | @@ -850,12 +855,15 @@ impl Section { | ... | @@ -850,12 +855,15 @@ impl Section { |
| 850 | } | 855 | } |
| 851 | }; | 856 | }; |
| 852 | let (saves, requests) = mpsc::channel(); | 857 | let (saves, requests) = mpsc::channel(); |
| 858 | let unsaved = Arc::new(AtomicUsize::new(0)); | ||
| 853 | let saver = { | 859 | let saver = { |
| 854 | let (replica, signal) = (Arc::clone(&replica), signal.clone()); | 860 | let (replica, signal) = (Arc::clone(&replica), signal.clone()); |
| 861 | let unsaved = Arc::clone(&unsaved); | ||
| 855 | thread::Builder::new() | 862 | thread::Builder::new() |
| 856 | .name("onestore-save".into()) | 863 | .name("onestore-save".into()) |
| 857 | .spawn(move || { | 864 | .spawn(move || { |
| 858 | save_pages(&replica, &requests, |outcome| { | 865 | save_pages(&replica, &requests, |outcome, count| { |
| 866 | unsaved.fetch_sub(count, Ordering::Release); | ||
| 859 | if saved_sender.send(outcome).is_ok() { | 867 | if saved_sender.send(outcome).is_ok() { |
| 860 | signal(); | 868 | signal(); |
| 861 | } | 869 | } |
| ... | @@ -889,6 +897,7 @@ impl Section { | ... | @@ -889,6 +897,7 @@ impl Section { |
| 889 | events, | 897 | events, |
| 890 | saves: Some(saves), | 898 | saves: Some(saves), |
| 891 | saved, | 899 | saved, |
| 900 | unsaved, | ||
| 892 | saver: Some(saver), | 901 | saver: Some(saver), |
| 893 | }) | 902 | }) |
| 894 | } | 903 | } |
| ... | @@ -930,10 +939,7 @@ impl Section { | ... | @@ -930,10 +939,7 @@ impl Section { |
| 930 | } | 939 | } |
| 931 | 940 | ||
| 932 | pub fn page(&self, space: ExGuid) -> Result<Page> { | 941 | pub fn page(&self, space: ExGuid) -> Result<Page> { |
| 933 | let snapshot = self.replica.snapshot()?; | 942 | self.replica.page(space) |
| 934 | let store = Store::parse(&snapshot)?; | ||
| 935 | let index = RevisionIndex::parse(&store)?; | ||
| 936 | Ok(Page::from_space(&Document::parse(&index)?, space)?) | ||
| 937 | } | 943 | } |
| 938 | 944 | ||
| 939 | /// Copies a page (usually from another section) to the end of this section as a | 945 | /// Copies a page (usually from another section) to the end of this section as a |
| ... | @@ -999,12 +1005,18 @@ impl Section { | ... | @@ -999,12 +1005,18 @@ impl Section { |
| 999 | after, | 1005 | after, |
| 1000 | author: author.to_owned(), | 1006 | author: author.to_owned(), |
| 1001 | }; | 1007 | }; |
| 1008 | self.unsaved.fetch_add(1, Ordering::Relaxed); | ||
| 1002 | self.saves | 1009 | self.saves |
| 1003 | .as_ref() | 1010 | .as_ref() |
| 1004 | .and_then(|saves| saves.send(save).ok()) | 1011 | .and_then(|saves| saves.send(save).ok()) |
| 1005 | .ok_or_else(|| io::Error::other("The save thread stopped").into()) | 1012 | .ok_or_else(|| io::Error::other("The save thread stopped").into()) |
| 1006 | } | 1013 | } |
| 1007 | 1014 | ||
| 1015 | /// Whether a queued save has yet to report, so the stored page may trail the editor's. | ||
| 1016 | pub fn saving(&self) -> bool { | ||
| 1017 | self.unsaved.load(Ordering::Acquire) > 0 | ||
| 1018 | } | ||
| 1019 | |||
| 1008 | pub fn status(&self, id: u64) -> Result<Option<EditStatus>> { | 1020 | pub fn status(&self, id: u64) -> Result<Option<EditStatus>> { |
| 1009 | self.replica.status(id) | 1021 | self.replica.status(id) |
| 1010 | } | 1022 | } |
| ... | @@ -1017,13 +1029,13 @@ impl Section { | ... | @@ -1017,13 +1029,13 @@ impl Section { |
| 1017 | /// attempt, or a conflict awaiting review. | 1029 | /// attempt, or a conflict awaiting review. |
| 1018 | pub fn queue(&self) -> Result<Vec<QueuedEdit>> { | 1030 | pub fn queue(&self) -> Result<Vec<QueuedEdit>> { |
| 1019 | self.replica | 1031 | self.replica |
| 1020 | .pending()? | 1032 | .queued()? |
| 1021 | .into_iter() | 1033 | .into_iter() |
| 1022 | .map(|edit| { | 1034 | .map(|(id, space)| { |
| 1023 | Ok(QueuedEdit { | 1035 | Ok(QueuedEdit { |
| 1024 | id: edit.id, | 1036 | id, |
| 1025 | space: edit.space, | 1037 | space, |
| 1026 | status: self.replica.status(edit.id)?.unwrap_or(EditStatus::Pending), | 1038 | status: self.replica.status(id)?.unwrap_or(EditStatus::Pending), |
| 1027 | }) | 1039 | }) |
| 1028 | }) | 1040 | }) |
| 1029 | .collect() | 1041 | .collect() |
| ... | @@ -1094,7 +1106,8 @@ impl Section { | ... | @@ -1094,7 +1106,8 @@ impl Section { |
| 1094 | } | 1106 | } |
| 1095 | } | 1107 | } |
| 1096 | 1108 | ||
| 1097 | pub fn replica(&self) -> &Replica { | 1109 | /// The replica, which other threads may read pages from while the section is open. |
| 1110 | pub fn replica(&self) -> &Arc<Replica> { | ||
| 1098 | &self.replica | 1111 | &self.replica |
| 1099 | } | 1112 | } |
| 1100 | 1113 | ||
| ... | @@ -1160,7 +1173,7 @@ fn save( | ... | @@ -1160,7 +1173,7 @@ fn save( |
| 1160 | fn save_pages( | 1173 | fn save_pages( |
| 1161 | replica: &Replica, | 1174 | replica: &Replica, |
| 1162 | requests: &Receiver<PageSave>, | 1175 | requests: &Receiver<PageSave>, |
| 1163 | report: impl Fn((ExGuid, std::result::Result<Save, String>)), | 1176 | report: impl Fn((ExGuid, std::result::Result<Save, String>), usize), |
| 1164 | ) { | 1177 | ) { |
| 1165 | let mut queue = VecDeque::new(); | 1178 | let mut queue = VecDeque::new(); |
| 1166 | // The last save's page and the working image it left. | 1179 | // The last save's page and the working image it left. |
| ... | @@ -1174,11 +1187,13 @@ fn save_pages( | ... | @@ -1174,11 +1187,13 @@ fn save_pages( |
| 1174 | } | 1187 | } |
| 1175 | queue.extend(requests.try_iter()); | 1188 | queue.extend(requests.try_iter()); |
| 1176 | let mut request = queue.pop_front().unwrap(); | 1189 | let mut request = queue.pop_front().unwrap(); |
| 1190 | let mut count = 1; | ||
| 1177 | while let Some(next) = queue.front() | 1191 | while let Some(next) = queue.front() |
| 1178 | && (next.space, &next.before, &next.author) | 1192 | && (next.space, &next.before, &next.author) |
| 1179 | == (request.space, &request.after, &request.author) | 1193 | == (request.space, &request.after, &request.author) |
| 1180 | { | 1194 | { |
| 1181 | request.after = queue.pop_front().unwrap().after; | 1195 | request.after = queue.pop_front().unwrap().after; |
| 1196 | count += 1; | ||
| 1182 | } | 1197 | } |
| 1183 | let known = last | 1198 | let known = last |
| 1184 | .take() | 1199 | .take() |
| ... | @@ -1200,7 +1215,7 @@ fn save_pages( | ... | @@ -1200,7 +1215,7 @@ fn save_pages( |
| 1200 | } | 1215 | } |
| 1201 | Err(error) => Err(error.to_string()), | 1216 | Err(error) => Err(error.to_string()), |
| 1202 | }; | 1217 | }; |
| 1203 | report((request.space, save)); | 1218 | report((request.space, save), count); |
| 1204 | } | 1219 | } |
| 1205 | } | 1220 | } |
| 1206 | 1221 | ||
| ... | @@ -1212,8 +1227,19 @@ impl Remote for FileRemote { | ... | @@ -1212,8 +1227,19 @@ impl Remote for FileRemote { |
| 1212 | onestore::read_file(&self.0) | 1227 | onestore::read_file(&self.0) |
| 1213 | } | 1228 | } |
| 1214 | 1229 | ||
| 1215 | fn publish(&mut self, edit: &PreparedEdit<'_>) -> std::result::Result<(), CommitError> { | 1230 | /// Read without the whole-file lock, which would block OneNote's readers: a change |
| 1216 | edit.commit_file(&self.0) | 1231 | /// detector, never a snapshot to edit. |
| 1232 | fn stamp(&mut self) -> io::Result<Option<Stamp>> { | ||
| 1233 | use std::io::Read; | ||
| 1234 | let mut file = std::fs::File::open(&self.0)?; | ||
| 1235 | let mut header = [0; 1024]; | ||
| 1236 | file.read_exact(&mut header)?; | ||
| 1237 | let length = file.metadata()?.len(); | ||
| 1238 | Ok(Some(Stamp { header, length })) | ||
| 1239 | } | ||
| 1240 | |||
| 1241 | fn publish(&mut self, transaction: &Transaction) -> std::result::Result<(), CommitError> { | ||
| 1242 | transaction.commit_file(&self.0) | ||
| 1217 | } | 1243 | } |
| 1218 | 1244 | ||
| 1219 | fn confirm(&mut self, snapshot: &[u8]) -> std::result::Result<(), CommitError> { | 1245 | fn confirm(&mut self, snapshot: &[u8]) -> std::result::Result<(), CommitError> { |
crates/notebook/src/smb/directory.rs+1| ... | @@ -38,6 +38,7 @@ impl Client { | ... | @@ -38,6 +38,7 @@ impl Client { |
| 38 | let file = File { | 38 | let file = File { |
| 39 | client: self, | 39 | client: self, |
| 40 | id: Some(response.file_id), | 40 | id: Some(response.file_id), |
| 41 | length: response.end_of_file, | ||
| 41 | }; | 42 | }; |
| 42 | let mut entries = BTreeMap::new(); | 43 | let mut entries = BTreeMap::new(); |
| 43 | loop { | 44 | loop { |
crates/notebook/src/smb/mod.rs+30-4| ... | @@ -218,6 +218,7 @@ impl Client { | ... | @@ -218,6 +218,7 @@ impl Client { |
| 218 | Ok(File { | 218 | Ok(File { |
| 219 | client: self, | 219 | client: self, |
| 220 | id: Some(response.file_id), | 220 | id: Some(response.file_id), |
| 221 | length: response.end_of_file, | ||
| 221 | }) | 222 | }) |
| 222 | } | 223 | } |
| 223 | 224 | ||
| ... | @@ -374,13 +375,36 @@ impl Client { | ... | @@ -374,13 +375,36 @@ impl Client { |
| 374 | }) | 375 | }) |
| 375 | } | 376 | } |
| 376 | 377 | ||
| 377 | /// Publishes a prepared edit using the same native writer coordination as text commits. | 378 | /// Publishes a transaction using the same native writer coordination as text commits. |
| 378 | pub fn commit_prepared( | 379 | pub fn commit_transaction( |
| 379 | &self, | 380 | &self, |
| 380 | path: &str, | 381 | path: &str, |
| 381 | edit: &onestore::PreparedEdit<'_>, | 382 | transaction: &onestore::Transaction, |
| 382 | ) -> Result<(), CommitError> { | 383 | ) -> Result<(), CommitError> { |
| 383 | self.commit(path, |file| edit.commit(file)) | 384 | self.commit(path, |file| transaction.commit(file)) |
| 385 | } | ||
| 386 | |||
| 387 | /// The header and length of a revision store, read without writer coordination or path | ||
| 388 | /// identity checks: a change detector for polling, never a snapshot to edit. | ||
| 389 | pub fn stamp(&self, path: &str) -> io::Result<onestore::Stamp> { | ||
| 390 | let mut file = self.open(path, false)?; | ||
| 391 | let mut header = [0; 1024]; | ||
| 392 | let mut read = 0; | ||
| 393 | let result = loop { | ||
| 394 | match file.read_at(read as u64, &mut header[read..]) { | ||
| 395 | Ok(0) => break Err(io::ErrorKind::UnexpectedEof.into()), | ||
| 396 | Ok(count) => read += count, | ||
| 397 | Err(error) => break Err(error), | ||
| 398 | } | ||
| 399 | if read == header.len() { | ||
| 400 | break Ok(()); | ||
| 401 | } | ||
| 402 | }; | ||
| 403 | let length = file.length; | ||
| 404 | let closed = file.close(); | ||
| 405 | result?; | ||
| 406 | closed?; | ||
| 407 | Ok(onestore::Stamp { header, length }) | ||
| 384 | } | 408 | } |
| 385 | 409 | ||
| 386 | /// Confirms an observed snapshot's durability under native writer coordination. | 410 | /// Confirms an observed snapshot's durability under native writer coordination. |
| ... | @@ -419,6 +443,8 @@ impl Drop for Client { | ... | @@ -419,6 +443,8 @@ impl Drop for Client { |
| 419 | struct File<'a> { | 443 | struct File<'a> { |
| 420 | client: &'a Client, | 444 | client: &'a Client, |
| 421 | id: Option<FileId>, | 445 | id: Option<FileId>, |
| 446 | /// The end of file when opened. | ||
| 447 | length: u64, | ||
| 422 | } | 448 | } |
| 423 | impl File<'_> { | 449 | impl File<'_> { |
| 424 | fn coordinate(self, path: &str, write: bool) -> io::Result<Self> { | 450 | fn coordinate(self, path: &str, write: bool) -> io::Result<Self> { |
crates/notebook/src/smb/remote.rs+7-3| ... | @@ -1,6 +1,6 @@ | ... | @@ -1,6 +1,6 @@ |
| 1 | use super::Client; | 1 | use super::Client; |
| 2 | use crate::Remote; | 2 | use crate::Remote; |
| 3 | use onestore::{CommitError, PreparedEdit}; | 3 | use onestore::{CommitError, Stamp, Transaction}; |
| 4 | use std::io; | 4 | use std::io; |
| 5 | 5 | ||
| 6 | /// Binds every reconciliation operation to one share-relative file and read limit. | 6 | /// Binds every reconciliation operation to one share-relative file and read limit. |
| ... | @@ -26,8 +26,12 @@ impl Remote for SmbRemote { | ... | @@ -26,8 +26,12 @@ impl Remote for SmbRemote { |
| 26 | self.client.read(&self.path, self.limit) | 26 | self.client.read(&self.path, self.limit) |
| 27 | } | 27 | } |
| 28 | 28 | ||
| 29 | fn publish(&mut self, edit: &PreparedEdit<'_>) -> Result<(), CommitError> { | 29 | fn stamp(&mut self) -> io::Result<Option<Stamp>> { |
| 30 | self.client.commit_prepared(&self.path, edit) | 30 | self.client.stamp(&self.path).map(Some) |
| 31 | } | ||
| 32 | |||
| 33 | fn publish(&mut self, transaction: &Transaction) -> Result<(), CommitError> { | ||
| 34 | self.client.commit_transaction(&self.path, transaction) | ||
| 31 | } | 35 | } |
| 32 | 36 | ||
| 33 | fn confirm(&mut self, snapshot: &[u8]) -> Result<(), CommitError> { | 37 | fn confirm(&mut self, snapshot: &[u8]) -> Result<(), CommitError> { |
crates/notebook/src/smb/tests.rs+48| ... | @@ -120,6 +120,7 @@ fn create(client: &Client, path: &str, bytes: &[u8]) { | ... | @@ -120,6 +120,7 @@ fn create(client: &Client, path: &str, bytes: &[u8]) { |
| 120 | let mut file = File { | 120 | let mut file = File { |
| 121 | client, | 121 | client, |
| 122 | id: Some(response.file_id), | 122 | id: Some(response.file_id), |
| 123 | length: response.end_of_file, | ||
| 123 | }; | 124 | }; |
| 124 | let mut offset = 0; | 125 | let mut offset = 0; |
| 125 | while offset < bytes.len() { | 126 | while offset < bytes.len() { |
| ... | @@ -344,6 +345,53 @@ fn live_coordination() { | ... | @@ -344,6 +345,53 @@ fn live_coordination() { |
| 344 | ); | 345 | ); |
| 345 | } | 346 | } |
| 346 | 347 | ||
| 348 | /// Polling reads only the header, even while maintenance excludes snapshot readers, and a | ||
| 349 | /// transaction commits against the header it was built on. | ||
| 350 | #[test] | ||
| 351 | #[ignore = "requires ONESTORE_SMB_LAB pointing to disposable Samba"] | ||
| 352 | fn live_stamp_and_transaction() { | ||
| 353 | let poller = client(); | ||
| 354 | let source = onestore::create_section("stamp.one", "Stamped", "Author").unwrap(); | ||
| 355 | let path = format!( | ||
| 356 | "stamp-{}.one", | ||
| 357 | SystemTime::now() | ||
| 358 | .duration_since(UNIX_EPOCH) | ||
| 359 | .unwrap() | ||
| 360 | .as_nanos() | ||
| 361 | ); | ||
| 362 | create(&poller, &path, &source); | ||
| 363 | assert_eq!( | ||
| 364 | poller.stamp(&path).unwrap(), | ||
| 365 | onestore::Stamp::of(&source).unwrap() | ||
| 366 | ); | ||
| 367 | let maintenance = client(); | ||
| 368 | let guard = maintenance.open(&path, false).unwrap(); | ||
| 369 | guard.lock(0xfffffffb, 0x12).unwrap(); | ||
| 370 | assert_eq!( | ||
| 371 | poller.read(&path, 1 << 20).unwrap_err().kind(), | ||
| 372 | io::ErrorKind::WouldBlock | ||
| 373 | ); | ||
| 374 | assert_eq!( | ||
| 375 | poller.stamp(&path).unwrap(), | ||
| 376 | onestore::Stamp::of(&source).unwrap() | ||
| 377 | ); | ||
| 378 | guard.close().unwrap(); | ||
| 379 | let (sid, oid, _) = text(&source); | ||
| 380 | let edit = onestore::PreparedEdit::text(&source, sid, oid, 0..0, "Changed ").unwrap(); | ||
| 381 | let transaction = edit.transaction(); | ||
| 382 | poller.commit_transaction(&path, &transaction).unwrap(); | ||
| 383 | assert_eq!( | ||
| 384 | poller.stamp(&path).unwrap(), | ||
| 385 | onestore::Stamp::of(edit.as_bytes()).unwrap() | ||
| 386 | ); | ||
| 387 | assert_eq!(poller.read(&path, 1 << 20).unwrap(), edit.as_bytes()); | ||
| 388 | let stale = poller.commit_transaction(&path, &transaction).unwrap_err(); | ||
| 389 | assert_eq!(stale.state, CommitState::NotCommitted); | ||
| 390 | assert_eq!(stale.error.kind(), io::ErrorKind::ResourceBusy); | ||
| 391 | assert_eq!(poller.read(&path, 1 << 20).unwrap(), edit.as_bytes()); | ||
| 392 | poller.delete(&path).unwrap(); | ||
| 393 | } | ||
| 394 | |||
| 347 | #[test] | 395 | #[test] |
| 348 | #[ignore = "requires ONESTORE_SMB_LAB pointing to disposable Samba"] | 396 | #[ignore = "requires ONESTORE_SMB_LAB pointing to disposable Samba"] |
| 349 | fn live_storage_inspection() { | 397 | fn live_storage_inspection() { |
crates/notebook/src/sync.rs+20-10| ... | @@ -1,5 +1,5 @@ | ... | @@ -1,5 +1,5 @@ |
| 1 | use super::*; | 1 | use super::*; |
| 2 | use onestore::{CommitError, CommitState}; | 2 | use onestore::{CommitError, CommitState, Stamp, Transaction}; |
| 3 | use rusqlite::OptionalExtension; | 3 | use rusqlite::OptionalExtension; |
| 4 | use std::{ | 4 | use std::{ |
| 5 | collections::BTreeMap, | 5 | collections::BTreeMap, |
| ... | @@ -7,10 +7,16 @@ use std::{ | ... | @@ -7,10 +7,16 @@ use std::{ |
| 7 | }; | 7 | }; |
| 8 | 8 | ||
| 9 | /// A single remote file with fresh reads and native-compatible guarded publication. | 9 | /// A single remote file with fresh reads and native-compatible guarded publication. |
| 10 | /// Errors retain publication state; confirmation compares, flushes, and notifies cached readers. | 10 | /// Errors retain publication state; confirmation checks the stamp, flushes, and notifies |
| 11 | /// cached readers. | ||
| 11 | pub trait Remote { | 12 | pub trait Remote { |
| 12 | fn read(&mut self) -> io::Result<Vec<u8>>; | 13 | fn read(&mut self) -> io::Result<Vec<u8>>; |
| 13 | fn publish(&mut self, edit: &PreparedEdit<'_>) -> std::result::Result<(), CommitError>; | 14 | /// The file's stamp without reading its body, when the remote can tell. While it is the |
| 15 | /// last observed image's, synchronization neither reads nor revalidates the file. | ||
| 16 | fn stamp(&mut self) -> io::Result<Option<Stamp>> { | ||
| 17 | Ok(None) | ||
| 18 | } | ||
| 19 | fn publish(&mut self, transaction: &Transaction) -> std::result::Result<(), CommitError>; | ||
| 14 | fn confirm(&mut self, snapshot: &[u8]) -> std::result::Result<(), CommitError>; | 20 | fn confirm(&mut self, snapshot: &[u8]) -> std::result::Result<(), CommitError>; |
| 15 | } | 21 | } |
| 16 | 22 | ||
| ... | @@ -78,13 +84,14 @@ impl Replica { | ... | @@ -78,13 +84,14 @@ impl Replica { |
| 78 | } | 84 | } |
| 79 | } | 85 | } |
| 80 | let _step = Step(self); | 86 | let _step = Step(self); |
| 81 | let snapshot = remote.read().map_err(Error::RemoteIo)?; | 87 | let base = self.remote_snapshot()?; |
| 82 | // An unchanged remote is the base image, validated when it was stored. | 88 | let changed = match remote.stamp().map_err(Error::RemoteIo)? { |
| 83 | let identity = if snapshot == self.remote_snapshot()? { | 89 | Some(stamp) if stamp == Stamp::of(&base)? => None, |
| 84 | None | 90 | _ => Some(remote.read().map_err(Error::RemoteIo)?).filter(|read| *read != base), |
| 85 | } else { | ||
| 86 | Some(validate(&snapshot)?) | ||
| 87 | }; | 91 | }; |
| 92 | // An unchanged remote is the base image, validated when it was stored. | ||
| 93 | let identity = changed.as_deref().map(validate).transpose()?; | ||
| 94 | let snapshot = changed.unwrap_or(base); | ||
| 88 | let (intent, attempted) = { | 95 | let (intent, attempted) = { |
| 89 | let mut connection = self | 96 | let mut connection = self |
| 90 | .connection | 97 | .connection |
| ... | @@ -92,6 +99,9 @@ impl Replica { | ... | @@ -92,6 +99,9 @@ impl Replica { |
| 92 | .map_err(|_| io::Error::other("Cache owner panicked"))?; | 99 | .map_err(|_| io::Error::other("Cache owner panicked"))?; |
| 93 | let transaction = | 100 | let transaction = |
| 94 | connection.transaction_with_behavior(TransactionBehavior::Immediate)?; | 101 | connection.transaction_with_behavior(TransactionBehavior::Immediate)?; |
| 102 | if identity.is_none() && images::settled(&transaction)? { | ||
| 103 | return Ok(None); | ||
| 104 | } | ||
| 95 | let (base, working) = images::both(&transaction)?; | 105 | let (base, working) = images::both(&transaction)?; |
| 96 | if let Some(identity) = identity | 106 | if let Some(identity) = identity |
| 97 | && RevisionIndex::parse(&Store::parse(&base)?)?.root != identity | 107 | && RevisionIndex::parse(&Store::parse(&base)?)?.root != identity |
| ... | @@ -267,7 +277,7 @@ impl Replica { | ... | @@ -267,7 +277,7 @@ impl Replica { |
| 267 | )?; | 277 | )?; |
| 268 | transaction.commit()?; | 278 | transaction.commit()?; |
| 269 | } | 279 | } |
| 270 | match remote.publish(&prepared) { | 280 | match remote.publish(&prepared.transaction()) { |
| 271 | Ok(()) => {} | 281 | Ok(()) => {} |
| 272 | Err(error) if error.state == CommitState::NotCommitted => { | 282 | Err(error) if error.state == CommitState::NotCommitted => { |
| 273 | let connection = self | 283 | let connection = self |
crates/notebook/src/worker.rs+14-3| ... | @@ -60,7 +60,8 @@ impl Drop for SyncWorker { | ... | @@ -60,7 +60,8 @@ impl Drop for SyncWorker { |
| 60 | 60 | ||
| 61 | impl Replica { | 61 | impl Replica { |
| 62 | /// Starts one worker, reconnecting through `connect` after transport failures. | 62 | /// Starts one worker, reconnecting through `connect` after transport failures. |
| 63 | /// Local edits wake it; `interval` controls idle polling and transport retries. | 63 | /// Local edits wake it; `interval` controls idle polling and transport retries. While |
| 64 | /// nothing is queued, a remote whose `stamp` holds is not read again. | ||
| 64 | /// Contended operations returning `NotCommitted` also back off by up to one second. | 65 | /// Contended operations returning `NotCommitted` also back off by up to one second. |
| 65 | /// `observe` runs on the worker after each attempt, including connection errors. | 66 | /// `observe` runs on the worker after each attempt, including connection errors. |
| 66 | /// Cache/document errors stop the worker; inspect them through `observe` or `stop`. | 67 | /// Cache/document errors stop the worker; inspect them through `observe` or `stop`. |
| ... | @@ -99,12 +100,22 @@ impl Replica { | ... | @@ -99,12 +100,22 @@ impl Replica { |
| 99 | let thread = thread::Builder::new() | 100 | let thread = thread::Builder::new() |
| 100 | .name("onestore-sync".into()) | 101 | .name("onestore-sync".into()) |
| 101 | .spawn(move || { | 102 | .spawn(move || { |
| 102 | let mut remote = None; | 103 | let mut remote: Option<R> = None; |
| 103 | let jitter = RandomState::new(); | 104 | let jitter = RandomState::new(); |
| 104 | let mut contention = 0_u32; | 105 | let mut contention = 0_u32; |
| 106 | // The first attempt always runs, so `observe` hears once that the remote is | ||
| 107 | // reachable. | ||
| 108 | let mut attempted = false; | ||
| 105 | while !worker_signal.stopped.load(Ordering::Acquire) { | 109 | while !worker_signal.stopped.load(Ordering::Acquire) { |
| 106 | let result = match remote.as_mut() { | 110 | let result = match remote.as_mut() { |
| 107 | Some(remote) => replica.sync_once(remote), | 111 | Some(remote) => { |
| 112 | if attempted && replica.settled(remote).unwrap_or(false) { | ||
| 113 | let _ = receiver.recv_timeout(interval); | ||
| 114 | continue; | ||
| 115 | } | ||
| 116 | attempted = true; | ||
| 117 | replica.sync_once(remote) | ||
| 118 | } | ||
| 108 | None => match connect() { | 119 | None => match connect() { |
| 109 | Ok(connected) => { | 120 | Ok(connected) => { |
| 110 | remote = Some(connected); | 121 | remote = Some(connected); |
crates/notebook/tests/assets.rs+1-1| ... | @@ -271,7 +271,7 @@ fn a_native_refresh_removing_the_reference_rejects_an_inflight_download() { | ... | @@ -271,7 +271,7 @@ fn a_native_refresh_removing_the_reference_rejects_an_inflight_download() { |
| 271 | fn read(&mut self) -> io::Result<Vec<u8>> { | 271 | fn read(&mut self) -> io::Result<Vec<u8>> { |
| 272 | fs::read("../../corpus/native-external-assets/native/synthetic.one") | 272 | fs::read("../../corpus/native-external-assets/native/synthetic.one") |
| 273 | } | 273 | } |
| 274 | fn publish(&mut self, _: &onestore::PreparedEdit<'_>) -> Result<(), onestore::CommitError> { | 274 | fn publish(&mut self, _: &onestore::Transaction) -> Result<(), onestore::CommitError> { |
| 275 | panic!("Unexpected publication") | 275 | panic!("Unexpected publication") |
| 276 | } | 276 | } |
| 277 | fn confirm(&mut self, _: &[u8]) -> Result<(), onestore::CommitError> { | 277 | fn confirm(&mut self, _: &[u8]) -> Result<(), onestore::CommitError> { |
crates/notebook/tests/support/model_schedule.rs+8-5| ... | @@ -5,7 +5,7 @@ use crate::model_ops::{self, AUTHOR}; | ... | @@ -5,7 +5,7 @@ use crate::model_ops::{self, AUTHOR}; |
| 5 | use crate::server::{Fault, Server}; | 5 | use crate::server::{Fault, Server}; |
| 6 | use notebook::{EditStatus, Operation, Remote, Replica}; | 6 | use notebook::{EditStatus, Operation, Remote, Replica}; |
| 7 | use onestore::{ | 7 | use onestore::{ |
| 8 | CommitError, ExGuid, PreparedEdit, RevisionIndex, Store, | 8 | CommitError, ExGuid, PreparedEdit, RevisionIndex, Store, Transaction, |
| 9 | document::Document, | 9 | document::Document, |
| 10 | page::{Outline, Page, PageObject}, | 10 | page::{Outline, Page, PageObject}, |
| 11 | }; | 11 | }; |
| ... | @@ -140,20 +140,23 @@ impl Remote for Session<'_> { | ... | @@ -140,20 +140,23 @@ impl Remote for Session<'_> { |
| 140 | self.server.read() | 140 | self.server.read() |
| 141 | } | 141 | } |
| 142 | 142 | ||
| 143 | fn publish(&mut self, edit: &PreparedEdit<'_>) -> Result<(), CommitError> { | 143 | fn publish(&mut self, transaction: &Transaction) -> Result<(), CommitError> { |
| 144 | assert!(!self.retired, "a retired attempt was replayed"); | 144 | assert!(!self.retired, "a retired attempt was replayed"); |
| 145 | self.publications += 1; | 145 | self.publications += 1; |
| 146 | assert_eq!(self.publications, 1); | 146 | assert_eq!(self.publications, 1); |
| 147 | let Some((_, Operation::Page(intent))) = &self.intent else { | 147 | let Some((_, Operation::Page(intent))) = &self.intent else { |
| 148 | panic!("the schedule queues page saves only") | 148 | panic!("the schedule queues page saves only") |
| 149 | }; | 149 | }; |
| 150 | if model_ops::page_of(&self.server.visible, SOURCE.1) == intent.before { | 150 | let mut image = self.server.visible.clone(); |
| 151 | if transaction.apply(&mut image).is_ok() | ||
| 152 | && model_ops::page_of(&self.server.visible, SOURCE.1) == intent.before | ||
| 153 | { | ||
| 151 | assert_eq!( | 154 | assert_eq!( |
| 152 | shape(&model_ops::page_of(edit.as_bytes(), SOURCE.1)), | 155 | shape(&model_ops::page_of(&image, SOURCE.1)), |
| 153 | shape(&intent.after) | 156 | shape(&intent.after) |
| 154 | ); | 157 | ); |
| 155 | } | 158 | } |
| 156 | self.server.publish(edit) | 159 | self.server.publish(transaction) |
| 157 | } | 160 | } |
| 158 | 161 | ||
| 159 | fn confirm(&mut self, snapshot: &[u8]) -> Result<(), CommitError> { | 162 | fn confirm(&mut self, snapshot: &[u8]) -> Result<(), CommitError> { |
crates/notebook/tests/support/page_schedule.rs+8-6| ... | @@ -1,7 +1,7 @@ | ... | @@ -1,7 +1,7 @@ |
| 1 | use crate::disk; | 1 | use crate::disk; |
| 2 | use notebook::{EditStatus, Operation, Remote, Replica}; | 2 | use notebook::{EditStatus, Operation, Remote, Replica}; |
| 3 | use onestore::{ | 3 | use onestore::{ |
| 4 | CommitError, ExGuid, PageEdit, PagePosition, PreparedEdit, RevisionIndex, Store, | 4 | CommitError, ExGuid, PageEdit, PagePosition, PreparedEdit, RevisionIndex, Store, Transaction, |
| 5 | document::{Document, Format, Kind, Layout}, | 5 | document::{Document, Format, Kind, Layout}, |
| 6 | page::{ | 6 | page::{ |
| 7 | Outline, Page, PageObject, PageParagraph, Paragraph, ParagraphContent, TextObject, | 7 | Outline, Page, PageObject, PageParagraph, Paragraph, ParagraphContent, TextObject, |
| ... | @@ -125,7 +125,9 @@ impl Remote for Session<'_> { | ... | @@ -125,7 +125,9 @@ impl Remote for Session<'_> { |
| 125 | Ok(self.disk.visible.clone()) | 125 | Ok(self.disk.visible.clone()) |
| 126 | } | 126 | } |
| 127 | 127 | ||
| 128 | fn publish(&mut self, edit: &PreparedEdit<'_>) -> Result<(), CommitError> { | 128 | fn publish(&mut self, transaction: &Transaction) -> Result<(), CommitError> { |
| 129 | let mut image = self.disk.visible.clone(); | ||
| 130 | transaction.apply(&mut image).unwrap(); | ||
| 129 | let mut expected = pages(&self.disk.visible); | 131 | let mut expected = pages(&self.disk.visible); |
| 130 | match self.operation.unwrap() { | 132 | match self.operation.unwrap() { |
| 131 | Operation::CreatePage(page) => expected.push((page.space(), page.object(), 1)), | 133 | Operation::CreatePage(page) => expected.push((page.space(), page.object(), 1)), |
| ... | @@ -144,7 +146,7 @@ impl Remote for Session<'_> { | ... | @@ -144,7 +146,7 @@ impl Remote for Session<'_> { |
| 144 | } | 146 | } |
| 145 | Operation::Page(intent) => { | 147 | Operation::Page(intent) => { |
| 146 | let body = body_text(&intent.after); | 148 | let body = body_text(&intent.after); |
| 147 | let store = Store::parse(edit.as_bytes()).unwrap(); | 149 | let store = Store::parse(&image).unwrap(); |
| 148 | let index = RevisionIndex::parse(&store).unwrap(); | 150 | let index = RevisionIndex::parse(&store).unwrap(); |
| 149 | let document = Document::parse(&index).unwrap(); | 151 | let document = Document::parse(&index).unwrap(); |
| 150 | let matching: Vec<_> = document | 152 | let matching: Vec<_> = document |
| ... | @@ -160,10 +162,10 @@ impl Remote for Session<'_> { | ... | @@ -160,10 +162,10 @@ impl Remote for Session<'_> { |
| 160 | } | 162 | } |
| 161 | _ => panic!(), | 163 | _ => panic!(), |
| 162 | } | 164 | } |
| 163 | assert_eq!(pages(edit.as_bytes()), expected); | 165 | assert_eq!(pages(&image), expected); |
| 164 | let old = current::current(&self.disk.durable); | 166 | let old = current::current(&self.disk.durable); |
| 165 | let new = current::current(edit.as_bytes()); | 167 | let new = current::current(&image); |
| 166 | let result = edit.commit(self.disk); | 168 | let result = transaction.commit(self.disk); |
| 167 | let observed = current::current(&self.disk.durable); | 169 | let observed = current::current(&self.disk.durable); |
| 168 | assert!(observed == old || observed == new); | 170 | assert!(observed == old || observed == new); |
| 169 | if result.is_ok() { | 171 | if result.is_ok() { |
crates/notebook/tests/support/server.rs+6-3| ... | @@ -3,7 +3,7 @@ | ... | @@ -3,7 +3,7 @@ |
| 3 | 3 | ||
| 4 | use notebook::Remote; | 4 | use notebook::Remote; |
| 5 | use onestore::{ | 5 | use onestore::{ |
| 6 | CommitError, CommitIo, CommitState, ExGuid, PreparedEdit, RevisionIndex, Store, | 6 | CommitError, CommitIo, CommitState, ExGuid, RevisionIndex, Stamp, Store, Transaction, |
| 7 | document::{Document, Kind}, | 7 | document::{Document, Kind}, |
| 8 | }; | 8 | }; |
| 9 | use std::io; | 9 | use std::io; |
| ... | @@ -75,7 +75,10 @@ impl Remote for Server { | ... | @@ -75,7 +75,10 @@ impl Remote for Server { |
| 75 | fn read(&mut self) -> io::Result<Vec<u8>> { | 75 | fn read(&mut self) -> io::Result<Vec<u8>> { |
| 76 | Ok(self.visible.clone()) | 76 | Ok(self.visible.clone()) |
| 77 | } | 77 | } |
| 78 | fn publish(&mut self, edit: &PreparedEdit<'_>) -> Result<(), CommitError> { | 78 | fn stamp(&mut self) -> io::Result<Option<Stamp>> { |
| 79 | Ok(Stamp::of(&self.visible).ok()) | ||
| 80 | } | ||
| 81 | fn publish(&mut self, transaction: &Transaction) -> Result<(), CommitError> { | ||
| 79 | self.publications += 1; | 82 | self.publications += 1; |
| 80 | let fault = std::mem::take(&mut self.fault); | 83 | let fault = std::mem::take(&mut self.fault); |
| 81 | match fault { | 84 | match fault { |
| ... | @@ -85,7 +88,7 @@ impl Remote for Server { | ... | @@ -85,7 +88,7 @@ impl Remote for Server { |
| 85 | _ => {} | 88 | _ => {} |
| 86 | } | 89 | } |
| 87 | let old = self.durable.clone(); | 90 | let old = self.durable.clone(); |
| 88 | edit.commit(self)?; | 91 | transaction.commit(self)?; |
| 89 | match fault { | 92 | match fault { |
| 90 | Fault::UnknownAfter => { | 93 | Fault::UnknownAfter => { |
| 91 | self.durable = old; | 94 | self.durable = old; |
crates/notebook/tests/sync.rs+50-5| ... | @@ -1,7 +1,7 @@ | ... | @@ -1,7 +1,7 @@ |
| 1 | //! Replica protocol: durable receipts, retired attempts, conflict review and contention. | 1 | //! Replica protocol: durable receipts, retired attempts, conflict review and contention. |
| 2 | 2 | ||
| 3 | use notebook::{ConflictKind, EditStatus, Error, Remote, Replica}; | 3 | use notebook::{ConflictKind, EditStatus, Error, Remote, Replica}; |
| 4 | use onestore::{CommitError, CommitState, ExGuid, PreparedEdit, page::Page}; | 4 | use onestore::{CommitError, CommitState, ExGuid, Transaction, page::Page}; |
| 5 | use std::{io, ops::Range}; | 5 | use std::{io, ops::Range}; |
| 6 | 6 | ||
| 7 | #[path = "support/server.rs"] | 7 | #[path = "support/server.rs"] |
| ... | @@ -25,6 +25,51 @@ fn save(cache: &Replica, text: ExGuid, range: Range<u32>, replacement: &str) -> | ... | @@ -25,6 +25,51 @@ fn save(cache: &Replica, text: ExGuid, range: Range<u32>, replacement: &str) -> |
| 25 | .unwrap() | 25 | .unwrap() |
| 26 | } | 26 | } |
| 27 | 27 | ||
| 28 | #[test] | ||
| 29 | fn an_unchanged_stamp_publishes_and_settles_without_reading_the_remote() { | ||
| 30 | struct Counted { | ||
| 31 | server: Server, | ||
| 32 | reads: usize, | ||
| 33 | } | ||
| 34 | impl Remote for Counted { | ||
| 35 | fn read(&mut self) -> io::Result<Vec<u8>> { | ||
| 36 | self.reads += 1; | ||
| 37 | self.server.read() | ||
| 38 | } | ||
| 39 | fn stamp(&mut self) -> io::Result<Option<onestore::Stamp>> { | ||
| 40 | self.server.stamp() | ||
| 41 | } | ||
| 42 | fn publish(&mut self, transaction: &Transaction) -> Result<(), CommitError> { | ||
| 43 | self.server.publish(transaction) | ||
| 44 | } | ||
| 45 | fn confirm(&mut self, snapshot: &[u8]) -> Result<(), CommitError> { | ||
| 46 | self.server.confirm(snapshot) | ||
| 47 | } | ||
| 48 | } | ||
| 49 | let directory = tempfile::tempdir().unwrap(); | ||
| 50 | let source = onestore::create_section("stamp.one", "Original", "Fixture").unwrap(); | ||
| 51 | let (sid, object, _) = text(&source); | ||
| 52 | let cache = Replica::create(directory.path().join("cache.sqlite"), &source).unwrap(); | ||
| 53 | let id = save(&cache, object, 0..0, "Local ").unwrap(); | ||
| 54 | let mut remote = Counted { | ||
| 55 | server: Server::new(&source), | ||
| 56 | reads: 0, | ||
| 57 | }; | ||
| 58 | assert!(matches!( | ||
| 59 | cache.sync_once(&mut remote).unwrap(), | ||
| 60 | Some((published, EditStatus::Published { .. })) if published == id | ||
| 61 | )); | ||
| 62 | assert_eq!(cache.sync_once(&mut remote).unwrap(), None); | ||
| 63 | assert_eq!(remote.reads, 0); | ||
| 64 | // Another writer's commit moves the header, so the next step reads the file. | ||
| 65 | let native = | ||
| 66 | onestore::replace_text(&remote.server.visible, sid, object, 0..0, "Native ").unwrap(); | ||
| 67 | remote.server.visible.clone_from(&native); | ||
| 68 | assert_eq!(cache.sync_once(&mut remote).unwrap(), None); | ||
| 69 | assert_eq!(remote.reads, 1); | ||
| 70 | assert_eq!(cache.snapshot().unwrap(), native); | ||
| 71 | } | ||
| 72 | |||
| 28 | #[test] | 73 | #[test] |
| 29 | fn recovery_archive_preserves_the_queue_uncertainty_and_receipts_without_becoming_a_writer() { | 74 | fn recovery_archive_preserves_the_queue_uncertainty_and_receipts_without_becoming_a_writer() { |
| 30 | use notebook::{Recovery, RecoverySummary}; | 75 | use notebook::{Recovery, RecoverySummary}; |
| ... | @@ -417,9 +462,9 @@ fn twelve_local_editors_progress_during_remote_reads_publication_and_confirmatio | ... | @@ -417,9 +462,9 @@ fn twelve_local_editors_progress_during_remote_reads_publication_and_confirmatio |
| 417 | self.wait("read"); | 462 | self.wait("read"); |
| 418 | self.server.read() | 463 | self.server.read() |
| 419 | } | 464 | } |
| 420 | fn publish(&mut self, edit: &PreparedEdit<'_>) -> Result<(), CommitError> { | 465 | fn publish(&mut self, transaction: &Transaction) -> Result<(), CommitError> { |
| 421 | self.wait("publish"); | 466 | self.wait("publish"); |
| 422 | self.server.publish(edit) | 467 | self.server.publish(transaction) |
| 423 | } | 468 | } |
| 424 | fn confirm(&mut self, source: &[u8]) -> Result<(), CommitError> { | 469 | fn confirm(&mut self, source: &[u8]) -> Result<(), CommitError> { |
| 425 | self.wait("confirm"); | 470 | self.wait("confirm"); |
| ... | @@ -768,8 +813,8 @@ fn remote_changes_after_review_cannot_be_overwritten_by_the_reviewed_page() { | ... | @@ -768,8 +813,8 @@ fn remote_changes_after_review_cannot_be_overwritten_by_the_reviewed_page() { |
| 768 | } | 813 | } |
| 769 | Ok(snapshot) | 814 | Ok(snapshot) |
| 770 | } | 815 | } |
| 771 | fn publish(&mut self, edit: &PreparedEdit<'_>) -> Result<(), CommitError> { | 816 | fn publish(&mut self, transaction: &Transaction) -> Result<(), CommitError> { |
| 772 | self.server.publish(edit) | 817 | self.server.publish(transaction) |
| 773 | } | 818 | } |
| 774 | fn confirm(&mut self, snapshot: &[u8]) -> Result<(), CommitError> { | 819 | fn confirm(&mut self, snapshot: &[u8]) -> Result<(), CommitError> { |
| 775 | self.server.confirm(snapshot) | 820 | self.server.confirm(snapshot) |
crates/notebook/tests/sync_page.rs+2| ... | @@ -209,6 +209,8 @@ fn surviving_page_revision_alone_does_not_confirm_section_publication() { | ... | @@ -209,6 +209,8 @@ fn surviving_page_revision_alone_does_not_confirm_section_publication() { |
| 209 | // Replacing only the revision identity models maintenance retiring the section proof. | 209 | // Replacing only the revision identity models maintenance retiring the section proof. |
| 210 | let at = positions[0] + 4; | 210 | let at = positions[0] + 4; |
| 211 | server.visible[at] ^= 0x40; | 211 | server.visible[at] ^= 0x40; |
| 212 | // A rewrite publishes a new file version (MS-ONESTORE 2.3.1). | ||
| 213 | server.visible[212] ^= 1; | ||
| 212 | let store = Store::parse(&server.visible).unwrap(); | 214 | let store = Store::parse(&server.visible).unwrap(); |
| 213 | let index = RevisionIndex::parse(&store).unwrap(); | 215 | let index = RevisionIndex::parse(&store).unwrap(); |
| 214 | index.validate_current().unwrap(); | 216 | index.validate_current().unwrap(); |
crates/notebook/tests/sync_paragraph.rs+12-2| ... | @@ -97,7 +97,9 @@ fn split_paragraph(page: &mut Page, text: ExGuid, offset: u32) { | ... | @@ -97,7 +97,9 @@ fn split_paragraph(page: &mut Page, text: ExGuid, offset: u32) { |
| 97 | } | 97 | } |
| 98 | 98 | ||
| 99 | /// Joins the paragraph carrying `right` into the one carrying `left`; an empty left text | 99 | /// Joins the paragraph carrying `right` into the one carrying `left`; an empty left text |
| 100 | /// object is replaced by a non-empty right one, which is the identity OneNote keeps. | 100 | /// object is replaced by a non-empty right one, which is the identity OneNote keeps. The |
| 101 | /// right paragraph's children keep their depth, under the left paragraph or the nearest of | ||
| 102 | /// its ancestors above them. | ||
| 101 | fn join_paragraphs(page: &mut Page, left: ExGuid, right: ExGuid) { | 103 | fn join_paragraphs(page: &mut Page, left: ExGuid, right: ExGuid) { |
| 102 | locate_in(page, left, |list, first| { | 104 | locate_in(page, left, |list, first| { |
| 103 | let second = at(list, right).expect("the joined paragraphs share a container"); | 105 | let second = at(list, right).expect("the joined paragraphs share a container"); |
| ... | @@ -115,9 +117,17 @@ fn join_paragraphs(page: &mut Page, left: ExGuid, right: ExGuid) { | ... | @@ -115,9 +117,17 @@ fn join_paragraphs(page: &mut Page, left: ExGuid, right: ExGuid) { |
| 115 | } | 117 | } |
| 116 | target.text = joined; | 118 | target.text = joined; |
| 117 | list.remove(second); | 119 | list.remove(second); |
| 120 | let nesting: std::collections::BTreeMap<_, _> = | ||
| 121 | list.iter().map(|p| (p.id, (p.level, p.parent))).collect(); | ||
| 118 | for paragraph in list { | 122 | for paragraph in list { |
| 119 | if paragraph.parent == Some(orphaned) { | 123 | if paragraph.parent == Some(orphaned) { |
| 120 | paragraph.parent = Some(parent); | 124 | let mut adopter = Some(parent); |
| 125 | while let Some(id) = adopter | ||
| 126 | && nesting[&id].0 >= paragraph.level | ||
| 127 | { | ||
| 128 | adopter = nesting[&id].1; | ||
| 129 | } | ||
| 130 | paragraph.parent = adopter; | ||
| 121 | } | 131 | } |
| 122 | } | 132 | } |
| 123 | }); | 133 | }); |
crates/notebook/tests/sync_worker.rs+14-11| ... | @@ -2,7 +2,7 @@ | ... | @@ -2,7 +2,7 @@ |
| 2 | 2 | ||
| 3 | use notebook::{ConflictKind, EditStatus, Error, Remote, Replica}; | 3 | use notebook::{ConflictKind, EditStatus, Error, Remote, Replica}; |
| 4 | use onestore::{ | 4 | use onestore::{ |
| 5 | CommitError, CommitState, ExGuid, PreparedEdit, RevisionIndex, Store, document::Document, | 5 | CommitError, CommitState, ExGuid, RevisionIndex, Store, Transaction, document::Document, |
| 6 | }; | 6 | }; |
| 7 | use std::io; | 7 | use std::io; |
| 8 | 8 | ||
| ... | @@ -25,8 +25,11 @@ impl Remote for Shared { | ... | @@ -25,8 +25,11 @@ impl Remote for Shared { |
| 25 | fn read(&mut self) -> io::Result<Vec<u8>> { | 25 | fn read(&mut self) -> io::Result<Vec<u8>> { |
| 26 | self.0.lock().unwrap().read() | 26 | self.0.lock().unwrap().read() |
| 27 | } | 27 | } |
| 28 | fn publish(&mut self, edit: &PreparedEdit<'_>) -> Result<(), CommitError> { | 28 | fn stamp(&mut self) -> io::Result<Option<onestore::Stamp>> { |
| 29 | self.0.lock().unwrap().publish(edit) | 29 | self.0.lock().unwrap().stamp() |
| 30 | } | ||
| 31 | fn publish(&mut self, transaction: &Transaction) -> Result<(), CommitError> { | ||
| 32 | self.0.lock().unwrap().publish(transaction) | ||
| 30 | } | 33 | } |
| 31 | fn confirm(&mut self, snapshot: &[u8]) -> Result<(), CommitError> { | 34 | fn confirm(&mut self, snapshot: &[u8]) -> Result<(), CommitError> { |
| 32 | self.0.lock().unwrap().confirm(snapshot) | 35 | self.0.lock().unwrap().confirm(snapshot) |
| ... | @@ -72,8 +75,8 @@ fn reconnects_after_connect_read_and_uncertain_publish_without_replaying() { | ... | @@ -72,8 +75,8 @@ fn reconnects_after_connect_read_and_uncertain_publish_without_replaying() { |
| 72 | } | 75 | } |
| 73 | self.shared.read() | 76 | self.shared.read() |
| 74 | } | 77 | } |
| 75 | fn publish(&mut self, edit: &PreparedEdit<'_>) -> Result<(), CommitError> { | 78 | fn publish(&mut self, transaction: &Transaction) -> Result<(), CommitError> { |
| 76 | self.shared.publish(edit) | 79 | self.shared.publish(transaction) |
| 77 | } | 80 | } |
| 78 | fn confirm(&mut self, snapshot: &[u8]) -> Result<(), CommitError> { | 81 | fn confirm(&mut self, snapshot: &[u8]) -> Result<(), CommitError> { |
| 79 | self.shared.confirm(snapshot) | 82 | self.shared.confirm(snapshot) |
| ... | @@ -235,10 +238,10 @@ fn dropping_during_publication_is_nonblocking_and_retains_ownership_until_recove | ... | @@ -235,10 +238,10 @@ fn dropping_during_publication_is_nonblocking_and_retains_ownership_until_recove |
| 235 | fn read(&mut self) -> io::Result<Vec<u8>> { | 238 | fn read(&mut self) -> io::Result<Vec<u8>> { |
| 236 | self.shared.read() | 239 | self.shared.read() |
| 237 | } | 240 | } |
| 238 | fn publish(&mut self, edit: &PreparedEdit<'_>) -> Result<(), CommitError> { | 241 | fn publish(&mut self, transaction: &Transaction) -> Result<(), CommitError> { |
| 239 | self.entered.send(()).unwrap(); | 242 | self.entered.send(()).unwrap(); |
| 240 | self.resume.recv_timeout(Duration::from_secs(5)).unwrap(); | 243 | self.resume.recv_timeout(Duration::from_secs(5)).unwrap(); |
| 241 | self.shared.publish(edit) | 244 | self.shared.publish(transaction) |
| 242 | } | 245 | } |
| 243 | fn confirm(&mut self, snapshot: &[u8]) -> Result<(), CommitError> { | 246 | fn confirm(&mut self, snapshot: &[u8]) -> Result<(), CommitError> { |
| 244 | self.shared.confirm(snapshot) | 247 | self.shared.confirm(snapshot) |
| ... | @@ -580,7 +583,7 @@ fn ordinary_read_and_unpublished_write_contention_reuse_the_connection() { | ... | @@ -580,7 +583,7 @@ fn ordinary_read_and_unpublished_write_contention_reuse_the_connection() { |
| 580 | _ => self.server.read(), | 583 | _ => self.server.read(), |
| 581 | } | 584 | } |
| 582 | } | 585 | } |
| 583 | fn publish(&mut self, edit: &PreparedEdit<'_>) -> Result<(), CommitError> { | 586 | fn publish(&mut self, transaction: &Transaction) -> Result<(), CommitError> { |
| 584 | self.writes += 1; | 587 | self.writes += 1; |
| 585 | match self.writes { | 588 | match self.writes { |
| 586 | 1 | 2 => Err(CommitError { | 589 | 1 | 2 => Err(CommitError { |
| ... | @@ -592,7 +595,7 @@ fn ordinary_read_and_unpublished_write_contention_reuse_the_connection() { | ... | @@ -592,7 +595,7 @@ fn ordinary_read_and_unpublished_write_contention_reuse_the_connection() { |
| 592 | } | 595 | } |
| 593 | .into(), | 596 | .into(), |
| 594 | }), | 597 | }), |
| 595 | _ => self.server.publish(edit), | 598 | _ => self.server.publish(transaction), |
| 596 | } | 599 | } |
| 597 | } | 600 | } |
| 598 | fn confirm(&mut self, source: &[u8]) -> Result<(), CommitError> { | 601 | fn confirm(&mut self, source: &[u8]) -> Result<(), CommitError> { |
| ... | @@ -638,7 +641,7 @@ fn publication_backoff_drains_local_wakes_without_waiting_for_the_idle_poll() { | ... | @@ -638,7 +641,7 @@ fn publication_backoff_drains_local_wakes_without_waiting_for_the_idle_poll() { |
| 638 | fn read(&mut self) -> io::Result<Vec<u8>> { | 641 | fn read(&mut self) -> io::Result<Vec<u8>> { |
| 639 | self.0.read() | 642 | self.0.read() |
| 640 | } | 643 | } |
| 641 | fn publish(&mut self, edit: &PreparedEdit<'_>) -> Result<(), CommitError> { | 644 | fn publish(&mut self, transaction: &Transaction) -> Result<(), CommitError> { |
| 642 | let server = &mut self.0; | 645 | let server = &mut self.0; |
| 643 | if server.publications == 0 { | 646 | if server.publications == 0 { |
| 644 | server.publications += 1; | 647 | server.publications += 1; |
| ... | @@ -647,7 +650,7 @@ fn publication_backoff_drains_local_wakes_without_waiting_for_the_idle_poll() { | ... | @@ -647,7 +650,7 @@ fn publication_backoff_drains_local_wakes_without_waiting_for_the_idle_poll() { |
| 647 | error: io::ErrorKind::ResourceBusy.into(), | 650 | error: io::ErrorKind::ResourceBusy.into(), |
| 648 | }); | 651 | }); |
| 649 | } | 652 | } |
| 650 | server.publish(edit) | 653 | server.publish(transaction) |
| 651 | } | 654 | } |
| 652 | fn confirm(&mut self, source: &[u8]) -> Result<(), CommitError> { | 655 | fn confirm(&mut self, source: &[u8]) -> Result<(), CommitError> { |
| 653 | self.0.confirm(source) | 656 | self.0.confirm(source) |
crates/onestore/README.md+9-4| ... | @@ -64,10 +64,13 @@ harness also accepts `--client-profile release`. | ... | @@ -64,10 +64,13 @@ harness also accepts `--client-profile release`. |
| 64 | | `OutlineEdit`, `PreparedEdit::outline` | Change ordinary outline position/width or a paragraph's saved expansion default, preserving identities and content | | 64 | | `OutlineEdit`, `PreparedEdit::outline` | Change ordinary outline position/width or a paragraph's saved expansion default, preserving identities and content | |
| 65 | | `PreparedEdit::page` | Publish an edited `page::Page` as one revision per changed space: text, character and paragraph formatting, hyperlinks (external and, via `page::link`, to pages, paragraphs and sections), bullets and numbering, note tags, table rows, columns, cell shading and indents, nested tables, inserted pictures (in paragraphs or on the page) and attachments, picture position, size and description, a stored attachment's shown name, source path and icon size, paragraph styles on new paragraphs, ink drawings and handwriting (strokes added and erased), equations (built from `page::Math` trees), paragraph insertion/split/join/move/deletion, outline insertion/deletion/position/width and saved collapse state, lowered onto the typed writers with the model's identities | | 65 | | `PreparedEdit::page` | Publish an edited `page::Page` as one revision per changed space: text, character and paragraph formatting, hyperlinks (external and, via `page::link`, to pages, paragraphs and sections), bullets and numbering, note tags, table rows, columns, cell shading and indents, nested tables, inserted pictures (in paragraphs or on the page) and attachments, picture position, size and description, a stored attachment's shown name, source path and icon size, paragraph styles on new paragraphs, ink drawings and handwriting (strokes added and erased), equations (built from `page::Math` trees), paragraph insertion/split/join/move/deletion, outline insertion/deletion/position/width and saved collapse state, lowered onto the typed writers with the model's identities | |
| 66 | | `TreeEdit`, `PreparedEdit::tree` | Move or delete a subtree on one page, normalize surviving containers, and replace an emptied table cell's paragraph atomically | | 66 | | `TreeEdit`, `PreparedEdit::tree` | Move or delete a subtree on one page, normalize surviving containers, and replace an emptied table cell's paragraph atomically | |
| 67 | | `PreparedEdit::commit`, `PreparedEdit::commit_file` | Publish the exact prepared image under caller-held exclusion or the conservative filesystem adapter | | 67 | | `Transaction`, `Stamp`, `PreparedEdit::transaction` | The bytes a commit writes (appended data, in-place list-tail and log patches, header) and the header and length it requires unchanged; `commit` under caller-held exclusion, `commit_file` under the conservative filesystem adapter, `apply` to the base image in memory; serializable for queues | |
| 68 | | `Arena`, `Section` | Keep a section parsed across edits: `open` validates an image once; `seal` appends one revision per changed space as a `Transaction` on `stamp`, checking only what it appends; `replay` applies a queued transaction; `image` and `page` read the result | | ||
| 69 | | `op::{Edit, Op, PageOp, TableEdit, SectionOp}`, `Section::apply` | Object-level edits (text, formatting, links, equations, paragraph insertion/split/join/move/deletion/levels, outlines, lists, tags, styles, paragraph formatting, tables, pictures, attachments, ink, page creation/import/moves/removal) applied whole or not to a kept-open section with emitter-chosen identities, UTF-16 ranges and the edit's time; refusals name the target, identity or structure at fault | | ||
| 70 | | `op::lower`, `op::lower_page`, `Section::apply_page` | The ops turning a range of paragraphs, or a whole page model, into another, computed from the models alone | | ||
| 68 | | `read_file` | Read a snapshot under whole-file exclusion | | 71 | | `read_file` | Read a snapshot under whole-file exclusion | |
| 69 | | `read_snapshot` | Read a validated snapshot through fresh positioned I/O while the caller excludes maintenance | | 72 | | `read_snapshot` | Read a validated snapshot through fresh positioned I/O while the caller excludes maintenance | |
| 70 | | `commit_file_property` | Lock, compare the source snapshot, append and flush, then publish the revision | | 73 | | `commit_file_property` | Lock, check the source snapshot's stamp, append and flush, then publish the revision | |
| 71 | | `CommitIo`, `commit_property_bytes` | Supply another storage backend with equivalent exclusion and ordered durability | | 74 | | `CommitIo`, `commit_property_bytes` | Supply another storage backend with equivalent exclusion and ordered durability | |
| 72 | 75 | ||
| 73 | Scalar edits accept encoded values and require the caller to maintain MS-ONE | 76 | Scalar edits accept encoded values and require the caller to maintain MS-ONE |
| ... | @@ -212,7 +215,7 @@ with optional explicit IDs when conflict copies contain identical text. | ... | @@ -212,7 +215,7 @@ with optional explicit IDs when conflict copies contain identical text. |
| 212 | ## Commit behavior | 215 | ## Commit behavior |
| 213 | 216 | ||
| 214 | ```text | 217 | ```text |
| 215 | exclusive lock → exact snapshot comparison | 218 | exclusive lock → header and length check |
| 216 | → append data → flush | 219 | → append data → flush |
| 217 | → prepare header metadata → flush | 220 | → prepare header metadata → flush |
| 218 | → publish transaction counter → flush | 221 | → publish transaction counter → flush |
| ... | @@ -220,7 +223,9 @@ exclusive lock → exact snapshot comparison | ... | @@ -220,7 +223,9 @@ exclusive lock → exact snapshot comparison |
| 220 | → notify cached readers → flush → unlock | 223 | → notify cached readers → flush → unlock |
| 221 | ``` | 224 | ``` |
| 222 | 225 | ||
| 223 | Stale snapshots fail before writing. Live readers must use equivalent exclusion. | 226 | Stale snapshots fail before writing: every committed transaction and placement rewrites |
| 227 | the header (MS-ONESTORE 2.3.1), so the body is never compared. Live readers must use | ||
| 228 | equivalent exclusion. | ||
| 224 | Native conflict creation can still expose cross-space references before their | 229 | Native conflict creation can still expose cross-space references before their |
| 225 | targets are saved; such snapshots must be rejected and reread while synchronization | 230 | targets are saved; such snapshots must be rejected and reread while synchronization |
| 226 | proceeds. `read_file` and `commit_file_property` serialize within the process because | 231 | proceeds. `read_file` and `commit_file_property` serialize within the process because |
crates/onestore/src/active.rs+181-55| ... | @@ -1,20 +1,27 @@ | ... | @@ -1,20 +1,27 @@ |
| 1 | use crate::{ | 1 | use crate::{ |
| 2 | Error, ExGuid, Object, RevisionIndex, Store, | 2 | Error, ExGuid, FileType, Object, RevisionIndex, Store, |
| 3 | document::{Document, Element, Revision, Space}, | 3 | document::{Document, Element, Kind, Revision, Space}, |
| 4 | write::{Commit, LiveRevision, PropertyObject, chain_depth, declared, differing}, | 4 | write::{Commit, LiveRevision, PropertyObject, chain_depth, declared, differing}, |
| 5 | }; | 5 | }; |
| 6 | use bumpalo::Bump; | 6 | use bumpalo::Bump; |
| 7 | use std::collections::{BTreeMap, BTreeSet}; | 7 | use std::{ |
| 8 | collections::{BTreeMap, BTreeSet}, | ||
| 9 | sync::Arc, | ||
| 10 | }; | ||
| 8 | 11 | ||
| 9 | type Result<T> = std::result::Result<T, Error>; | 12 | type Result<T> = std::result::Result<T, Error>; |
| 10 | 13 | ||
| 11 | /// Objects a writer stores in one revision of a page space. | 14 | /// Objects a writer stores in one revision of a page space. |
| 12 | pub(crate) type Changes = BTreeMap<ExGuid, PropertyObject>; | 15 | pub(crate) type Changes = BTreeMap<ExGuid, PropertyObject>; |
| 13 | 16 | ||
| 17 | /// Payloads a store holds, by identity. | ||
| 18 | pub(crate) type Files<'a> = std::rc::Rc<dyn Fn([u8; 16]) -> Result<&'a [u8]> + 'a>; | ||
| 19 | |||
| 14 | /// A page space's active revision with the views its writers read, kept as appending | 20 | /// A page space's active revision with the views its writers read, kept as appending |
| 15 | /// their revisions and parsing the result would leave it. | 21 | /// their revisions and parsing the result would leave it. |
| 22 | #[derive(Clone)] | ||
| 16 | pub(crate) struct ActivePage<'a> { | 23 | pub(crate) struct ActivePage<'a> { |
| 17 | store: &'a Store<'a>, | 24 | files: Files<'a>, |
| 18 | pub pages: Vec<ExGuid>, | 25 | pub pages: Vec<ExGuid>, |
| 19 | pub live: LiveRevision<'a>, | 26 | pub live: LiveRevision<'a>, |
| 20 | /// Elements of the reachable objects. | 27 | /// Elements of the reachable objects. |
| ... | @@ -42,6 +49,28 @@ impl<'a> ActivePage<'a> { | ... | @@ -42,6 +49,28 @@ impl<'a> ActivePage<'a> { |
| 42 | })?; | 49 | })?; |
| 43 | let rid = index.active(space)?; | 50 | let rid = index.active(space)?; |
| 44 | let live = LiveRevision::new(index.resolve(space, rid)?, chain_depth(index, space, rid))?; | 51 | let live = LiveRevision::new(index.resolve(space, rid)?, chain_depth(index, space, rid))?; |
| 52 | let store: &'a Store<'a> = index.store; | ||
| 53 | Self::viewed(std::rc::Rc::new(|guid| store.file_data(guid)), pages, live, view) | ||
| 54 | } | ||
| 55 | |||
| 56 | /// The page space `live` holds, whose stored payloads `files` reads. | ||
| 57 | pub(crate) fn open( | ||
| 58 | space: ExGuid, | ||
| 59 | live: LiveRevision<'a>, | ||
| 60 | file_type: FileType, | ||
| 61 | files: Files<'a>, | ||
| 62 | ) -> Result<Self> { | ||
| 63 | let (view, _) = Revision::parse(space, &live.revision, file_type, &mut |guid| files(guid))?; | ||
| 64 | let pages = manifest_pages(&view); | ||
| 65 | Self::viewed(files, pages, live, view) | ||
| 66 | } | ||
| 67 | |||
| 68 | fn viewed( | ||
| 69 | files: Files<'a>, | ||
| 70 | pages: Vec<ExGuid>, | ||
| 71 | live: LiveRevision<'a>, | ||
| 72 | view: Revision<'a>, | ||
| 73 | ) -> Result<Self> { | ||
| 45 | let parents = view.parents(&pages)?; | 74 | let parents = view.parents(&pages)?; |
| 46 | let titles = view | 75 | let titles = view |
| 47 | .nodes | 76 | .nodes |
| ... | @@ -50,7 +79,7 @@ impl<'a> ActivePage<'a> { | ... | @@ -50,7 +79,7 @@ impl<'a> ActivePage<'a> { |
| 50 | .map(|(id, _)| *id) | 79 | .map(|(id, _)| *id) |
| 51 | .collect(); | 80 | .collect(); |
| 52 | Ok(Self { | 81 | Ok(Self { |
| 53 | store: index.store, | 82 | files, |
| 54 | pages, | 83 | pages, |
| 55 | live, | 84 | live, |
| 56 | view, | 85 | view, |
| ... | @@ -65,13 +94,13 @@ impl<'a> ActivePage<'a> { | ... | @@ -65,13 +94,13 @@ impl<'a> ActivePage<'a> { |
| 65 | where | 94 | where |
| 66 | 'a: 'o, | 95 | 'a: 'o, |
| 67 | { | 96 | { |
| 68 | Element::parse_with(object, self.store, &mut |guid| match self | 97 | Element::parse_with(object, FileType::Section, &mut |guid| match self |
| 69 | .payloads | 98 | .payloads |
| 70 | .iter() | 99 | .iter() |
| 71 | .find(|(id, _)| *id == guid) | 100 | .find(|(id, _)| *id == guid) |
| 72 | { | 101 | { |
| 73 | Some((_, payload)) => Ok(*payload), | 102 | Some((_, payload)) => Ok(*payload), |
| 74 | None => self.store.file_data(guid), | 103 | None => (self.files)(guid), |
| 75 | }) | 104 | }) |
| 76 | } | 105 | } |
| 77 | 106 | ||
| ... | @@ -108,12 +137,23 @@ impl<'a> ActivePage<'a> { | ... | @@ -108,12 +137,23 @@ impl<'a> ActivePage<'a> { |
| 108 | payloads: &[([u8; 16], &[u8])], | 137 | payloads: &[([u8; 16], &[u8])], |
| 109 | changes: Changes, | 138 | changes: Changes, |
| 110 | ) -> Result<bool> { | 139 | ) -> Result<bool> { |
| 140 | Ok(self.store(arena, payloads, changes)?.is_some()) | ||
| 141 | } | ||
| 142 | |||
| 143 | /// `write`, returning the objects whose content, reachability or reference count may | ||
| 144 | /// have moved; none when it stores nothing. | ||
| 145 | pub(crate) fn store( | ||
| 146 | &mut self, | ||
| 147 | arena: &'a Bump, | ||
| 148 | payloads: &[([u8; 16], &[u8])], | ||
| 149 | changes: Changes, | ||
| 150 | ) -> Result<Option<BTreeSet<ExGuid>>> { | ||
| 111 | let changes = self.live.prepare(changes, true)?; | 151 | let changes = self.live.prepare(changes, true)?; |
| 112 | for (guid, payload) in payloads { | 152 | for (guid, payload) in payloads { |
| 113 | self.payloads.push((*guid, arena.alloc_slice_copy(payload))); | 153 | self.payloads.push((*guid, arena.alloc_slice_copy(payload))); |
| 114 | } | 154 | } |
| 115 | if changes.is_empty() { | 155 | if changes.is_empty() { |
| 116 | return Ok(!payloads.is_empty()); | 156 | return Ok((!payloads.is_empty()).then(BTreeSet::new)); |
| 117 | } | 157 | } |
| 118 | let mut objects = Vec::new(); | 158 | let mut objects = Vec::new(); |
| 119 | for (id, change) in changes { | 159 | for (id, change) in changes { |
| ... | @@ -132,8 +172,49 @@ impl<'a> ActivePage<'a> { | ... | @@ -132,8 +172,49 @@ impl<'a> ActivePage<'a> { |
| 132 | } else { | 172 | } else { |
| 133 | self.live.settle(changed)?; | 173 | self.live.settle(changed)?; |
| 134 | } | 174 | } |
| 135 | self.refresh(&replaced, touched)?; | 175 | self.refresh(&replaced, touched.clone())?; |
| 136 | Ok(true) | 176 | Ok(Some(touched)) |
| 177 | } | ||
| 178 | |||
| 179 | /// Takes the stored form of `ids` from `file`, the revision a seal appended, where the | ||
| 180 | /// revisions written here since the previous seal left them otherwise: a seal groups | ||
| 181 | /// tables and aliases read-only objects across all of them at once. | ||
| 182 | pub(crate) fn adopt( | ||
| 183 | &mut self, | ||
| 184 | file: &LiveRevision<'a>, | ||
| 185 | ids: impl IntoIterator<Item = ExGuid>, | ||
| 186 | ) -> Result<()> { | ||
| 187 | let mut replacements = Vec::new(); | ||
| 188 | for id in ids { | ||
| 189 | let Some(stored) = file.revision.objects.get(&id) else { | ||
| 190 | continue; | ||
| 191 | }; | ||
| 192 | match self.live.revision.objects.get_mut(&id) { | ||
| 193 | Some(object) if object.jcid == stored.jcid && object.data == stored.data => { | ||
| 194 | object.global_ids = Arc::clone(&stored.global_ids); | ||
| 195 | object.reference_count = stored.reference_count; | ||
| 196 | } | ||
| 197 | _ if file.is_reachable(id) => replacements.push((id, stored.clone())), | ||
| 198 | // Nothing reads an object the file leaves unreachable. | ||
| 199 | _ => {} | ||
| 200 | } | ||
| 201 | } | ||
| 202 | if !replacements.is_empty() { | ||
| 203 | let replaced = replacements.iter().map(|(id, _)| *id).collect(); | ||
| 204 | let Commit { touched, .. } = self.live.commit(replacements)?; | ||
| 205 | for id in &touched { | ||
| 206 | if let (Some(object), Some(stored)) = ( | ||
| 207 | self.live.revision.objects.get_mut(id), | ||
| 208 | file.revision.objects.get(id), | ||
| 209 | ) { | ||
| 210 | object.global_ids = Arc::clone(&stored.global_ids); | ||
| 211 | object.reference_count = stored.reference_count; | ||
| 212 | } | ||
| 213 | } | ||
| 214 | self.refresh(&replaced, touched)?; | ||
| 215 | } | ||
| 216 | self.live.depth = file.depth; | ||
| 217 | Ok(()) | ||
| 137 | } | 218 | } |
| 138 | 219 | ||
| 139 | /// Brings the view, parents and titles up to date with the revision for the objects a | 220 | /// Brings the view, parents and titles up to date with the revision for the objects a |
| ... | @@ -215,6 +296,24 @@ impl<'a> ActivePage<'a> { | ... | @@ -215,6 +296,24 @@ impl<'a> ActivePage<'a> { |
| 215 | } | 296 | } |
| 216 | } | 297 | } |
| 217 | 298 | ||
| 299 | /// The pages a page space's manifest lists. | ||
| 300 | pub(crate) fn manifest_pages(view: &Revision<'_>) -> Vec<ExGuid> { | ||
| 301 | match view.roots.get(&1).and_then(|id| view.nodes.get(id)) { | ||
| 302 | Some(manifest) if matches!(manifest.kind, Kind::Manifest { .. }) => manifest | ||
| 303 | .content | ||
| 304 | .iter() | ||
| 305 | .filter(|id| { | ||
| 306 | matches!( | ||
| 307 | view.nodes.get(id).map(|node| &node.kind), | ||
| 308 | Some(Kind::Page { .. }) | ||
| 309 | ) | ||
| 310 | }) | ||
| 311 | .copied() | ||
| 312 | .collect(), | ||
| 313 | _ => Vec::new(), | ||
| 314 | } | ||
| 315 | } | ||
| 316 | |||
| 218 | fn edges<'n>(node: &'n Element<'_>) -> impl Iterator<Item = ExGuid> + 'n { | 317 | fn edges<'n>(node: &'n Element<'_>) -> impl Iterator<Item = ExGuid> + 'n { |
| 219 | node.children | 318 | node.children |
| 220 | .iter() | 319 | .iter() |
| ... | @@ -245,10 +344,79 @@ pub(crate) fn write( | ... | @@ -245,10 +344,79 @@ pub(crate) fn write( |
| 245 | } | 344 | } |
| 246 | 345 | ||
| 247 | #[cfg(test)] | 346 | #[cfg(test)] |
| 248 | mod tests { | 347 | pub(crate) mod tests { |
| 249 | use super::*; | 348 | use super::*; |
| 250 | use crate::{Insertion, ParagraphSplit, TextAttribute, TreeEdit, document::Kind}; | 349 | use crate::{Insertion, ParagraphSplit, TextAttribute, TreeEdit, document::Kind}; |
| 251 | 350 | ||
| 351 | /// A seeded source of the typed writers' changes: insertions, moves, deletions, splits | ||
| 352 | /// and formatting of what a page holds. | ||
| 353 | pub(crate) struct Writes(pub u64); | ||
| 354 | |||
| 355 | impl Writes { | ||
| 356 | fn pick(&mut self, count: usize) -> usize { | ||
| 357 | self.0 = self | ||
| 358 | .0 | ||
| 359 | .wrapping_mul(6364136223846793005) | ||
| 360 | .wrapping_add(1442695040888963407); | ||
| 361 | (self.0 >> 33) as usize % count.max(1) | ||
| 362 | } | ||
| 363 | |||
| 364 | pub(crate) fn next(&mut self, active: &ActivePage<'_>) -> Result<Changes> { | ||
| 365 | let nodes = &active.view.nodes; | ||
| 366 | let listed = |kind: fn(&Kind<'_>) -> bool| -> Vec<ExGuid> { | ||
| 367 | nodes | ||
| 368 | .iter() | ||
| 369 | .filter(|(id, node)| kind(&node.kind) && active.parents.contains_key(id)) | ||
| 370 | .map(|(id, _)| *id) | ||
| 371 | .collect() | ||
| 372 | }; | ||
| 373 | let paragraphs = listed(|kind| matches!(kind, Kind::Paragraph { .. })); | ||
| 374 | let containers = listed(|kind| { | ||
| 375 | matches!( | ||
| 376 | kind, | ||
| 377 | Kind::Paragraph { .. } | Kind::Outline { .. } | Kind::Cell { .. } | ||
| 378 | ) | ||
| 379 | }); | ||
| 380 | let texts = listed(|kind| matches!(kind, Kind::RichText { .. })); | ||
| 381 | let unavailable = Error { | ||
| 382 | offset: 0, | ||
| 383 | message: "The page has nothing to edit", | ||
| 384 | }; | ||
| 385 | let paragraph = *paragraphs | ||
| 386 | .get(self.pick(paragraphs.len())) | ||
| 387 | .ok_or(unavailable)?; | ||
| 388 | let container = *containers | ||
| 389 | .get(self.pick(containers.len())) | ||
| 390 | .ok_or(unavailable)?; | ||
| 391 | let anchor = nodes[&container].children.get(self.pick(4)).copied(); | ||
| 392 | let text = *texts.get(self.pick(texts.len())).ok_or(unavailable)?; | ||
| 393 | let words = ["", "a", "Two words", "東京 🦀", "longer text here"]; | ||
| 394 | let word = words[self.pick(words.len())]; | ||
| 395 | match self.pick(8) { | ||
| 396 | 0..=3 => Insertion::paragraph(container, anchor, word, "Author") | ||
| 397 | .and_then(|insertion| insertion.changes(active)), | ||
| 398 | 4 => TreeEdit::move_to(paragraph, container, anchor, "Author") | ||
| 399 | .and_then(|edit| edit.changes(active)), | ||
| 400 | 5 => TreeEdit::delete(paragraph, "Author").and_then(|edit| edit.changes(active)), | ||
| 401 | 6 => { | ||
| 402 | let at = self.pick(3) as u32; | ||
| 403 | ParagraphSplit::new(text, at, "Author").and_then(|split| split.changes(active)) | ||
| 404 | } | ||
| 405 | _ => { | ||
| 406 | let end = self.pick(2) as u32; | ||
| 407 | let bold = self.pick(2) == 0; | ||
| 408 | crate::formatting::format_changes( | ||
| 409 | active, | ||
| 410 | text, | ||
| 411 | 0..end, | ||
| 412 | &[TextAttribute::Bold(bold)], | ||
| 413 | &[], | ||
| 414 | ) | ||
| 415 | } | ||
| 416 | } | ||
| 417 | } | ||
| 418 | } | ||
| 419 | |||
| 252 | /// `active` equals the page `image` stores, views included. | 420 | /// `active` equals the page `image` stores, views included. |
| 253 | fn assert_stores(active: &ActivePage<'_>, image: &[u8], space: ExGuid) { | 421 | fn assert_stores(active: &ActivePage<'_>, image: &[u8], space: ExGuid) { |
| 254 | let store = Store::parse(image).unwrap(); | 422 | let store = Store::parse(image).unwrap(); |
| ... | @@ -288,51 +456,9 @@ mod tests { | ... | @@ -288,51 +456,9 @@ mod tests { |
| 288 | let (space, _) = Document::parse(&index).unwrap().pages().unwrap()[0]; | 456 | let (space, _) = Document::parse(&index).unwrap().pages().unwrap()[0]; |
| 289 | let arena = Bump::new(); | 457 | let arena = Bump::new(); |
| 290 | let mut active = ActivePage::parse(&index, space).unwrap(); | 458 | let mut active = ActivePage::parse(&index, space).unwrap(); |
| 291 | let mut seed = 7_u64; | 459 | let mut writes = Writes(7); |
| 292 | let mut pick = |count: usize| { | ||
| 293 | seed = seed | ||
| 294 | .wrapping_mul(6364136223846793005) | ||
| 295 | .wrapping_add(1442695040888963407); | ||
| 296 | (seed >> 33) as usize % count.max(1) | ||
| 297 | }; | ||
| 298 | for step in 0..steps { | 460 | for step in 0..steps { |
| 299 | let nodes = &active.view.nodes; | 461 | let changes = writes.next(&active); |
| 300 | let listed = |kind: fn(&Kind<'_>) -> bool| -> Vec<ExGuid> { | ||
| 301 | nodes | ||
| 302 | .iter() | ||
| 303 | .filter(|(id, node)| kind(&node.kind) && active.parents.contains_key(id)) | ||
| 304 | .map(|(id, _)| *id) | ||
| 305 | .collect() | ||
| 306 | }; | ||
| 307 | let paragraphs = listed(|kind| matches!(kind, Kind::Paragraph { .. })); | ||
| 308 | let containers = listed(|kind| { | ||
| 309 | matches!( | ||
| 310 | kind, | ||
| 311 | Kind::Paragraph { .. } | Kind::Outline { .. } | Kind::Cell { .. } | ||
| 312 | ) | ||
| 313 | }); | ||
| 314 | let texts = listed(|kind| matches!(kind, Kind::RichText { .. })); | ||
| 315 | let paragraph = paragraphs[pick(paragraphs.len())]; | ||
| 316 | let container = containers[pick(containers.len())]; | ||
| 317 | let anchor = nodes[&container].children.get(pick(4)).copied(); | ||
| 318 | let text = texts[pick(texts.len())]; | ||
| 319 | let words = ["", "a", "Two words", "東京 🦀", "longer text here"]; | ||
| 320 | let word = words[pick(words.len())]; | ||
| 321 | let changes = match pick(8) { | ||
| 322 | 0..=3 => Insertion::paragraph(container, anchor, word, "Author") | ||
| 323 | .and_then(|insertion| insertion.changes(&active)), | ||
| 324 | 4 => TreeEdit::move_to(paragraph, container, anchor, "Author") | ||
| 325 | .and_then(|edit| edit.changes(&active)), | ||
| 326 | 5 => TreeEdit::delete(paragraph, "Author").and_then(|edit| edit.changes(&active)), | ||
| 327 | 6 => ParagraphSplit::new(text, pick(3) as u32, "Author") | ||
| 328 | .and_then(|split| split.changes(&active)), | ||
| 329 | _ => crate::formatting::format_changes( | ||
| 330 | &active, | ||
| 331 | text, | ||
| 332 | 0..pick(2) as u32, | ||
| 333 | &[TextAttribute::Bold(pick(2) == 0)], | ||
| 334 | ), | ||
| 335 | }; | ||
| 336 | let Ok(changes) = changes else { | 462 | let Ok(changes) = changes else { |
| 337 | continue; | 463 | continue; |
| 338 | }; | 464 | }; |
crates/onestore/src/commit.rs+197-81| ... | @@ -383,22 +383,20 @@ impl<'a> PreparedEdit<'a> { | ... | @@ -383,22 +383,20 @@ impl<'a> PreparedEdit<'a> { |
| 383 | &self.written | 383 | &self.written |
| 384 | } | 384 | } |
| 385 | 385 | ||
| 386 | /// Publishes these prepared bytes after comparing the entire original snapshot. | 386 | /// The bytes publishing this edit writes, against its snapshot's stamp. |
| 387 | /// The caller must retain OneNote-compatible exclusion through the returned outcome. | 387 | pub fn transaction(&self) -> Transaction { |
| 388 | Transaction::between(self.source, &self.written) | ||
| 389 | } | ||
| 390 | |||
| 391 | /// `Transaction::commit` of this edit. | ||
| 388 | pub fn commit(&self, io: &mut impl CommitIo) -> Result<(), CommitError> { | 392 | pub fn commit(&self, io: &mut impl CommitIo) -> Result<(), CommitError> { |
| 389 | commit_bytes(io, self.source, &self.written) | 393 | self.transaction().commit(io) |
| 390 | } | 394 | } |
| 391 | 395 | ||
| 392 | /// Publishes these exact bytes through the conservative whole-file filesystem adapter. | 396 | /// `Transaction::commit_file` of this edit. |
| 393 | /// A changed source returns ResourceBusy; an uncertain outcome must be reconciled before replay. | ||
| 394 | #[cfg(any(unix, windows))] | 397 | #[cfg(any(unix, windows))] |
| 395 | pub fn commit_file(&self, path: impl AsRef<Path>) -> Result<(), CommitError> { | 398 | pub fn commit_file(&self, path: impl AsRef<Path>) -> Result<(), CommitError> { |
| 396 | let mut io = FileIo::open(path, true).map_err(|error| CommitError { | 399 | self.transaction().commit_file(path) |
| 397 | state: CommitState::NotCommitted, | ||
| 398 | error, | ||
| 399 | })?; | ||
| 400 | let result = self.commit(&mut io); | ||
| 401 | io.finish(result) | ||
| 402 | } | 400 | } |
| 403 | } | 401 | } |
| 404 | 402 | ||
| ... | @@ -413,7 +411,7 @@ pub fn confirm_file_snapshot(path: impl AsRef<Path>, source: &[u8]) -> Result<() | ... | @@ -413,7 +411,7 @@ pub fn confirm_file_snapshot(path: impl AsRef<Path>, source: &[u8]) -> Result<() |
| 413 | io.finish(result) | 411 | io.finish(result) |
| 414 | } | 412 | } |
| 415 | 413 | ||
| 416 | /// Compares and flushes a snapshot, then refreshes its header version metadata. | 414 | /// Checks and flushes a snapshot's stamp, then refreshes its header version metadata. |
| 417 | /// No revision is added; reread before using the snapshot for another physical commit. | 415 | /// No revision is added; reread before using the snapshot for another physical commit. |
| 418 | /// The caller must hold OneNote-compatible exclusion and independently establish which | 416 | /// The caller must hold OneNote-compatible exclusion and independently establish which |
| 419 | /// intents the snapshot contains. A successful read alone is not a durable acknowledgement. | 417 | /// intents the snapshot contains. A successful read alone is not a durable acknowledgement. |
| ... | @@ -429,7 +427,7 @@ pub fn confirm_snapshot(io: &mut impl CommitIo, source: &[u8]) -> Result<(), Com | ... | @@ -429,7 +427,7 @@ pub fn confirm_snapshot(io: &mut impl CommitIo, source: &[u8]) -> Result<(), Com |
| 429 | version[..16].copy_from_slice(&crate::write::fresh_guid().map_err(io::Error::other)?); | 427 | version[..16].copy_from_slice(&crate::write::fresh_guid().map_err(io::Error::other)?); |
| 430 | version[16..24].copy_from_slice(&generation.to_le_bytes()); | 428 | version[16..24].copy_from_slice(&generation.to_le_bytes()); |
| 431 | version[24..].copy_from_slice(&crate::write::fresh_guid().map_err(io::Error::other)?); | 429 | version[24..].copy_from_slice(&crate::write::fresh_guid().map_err(io::Error::other)?); |
| 432 | compare_snapshot(io, source)?; | 430 | Stamp::of(source).map_err(io::Error::other)?.check(io)?; |
| 433 | state = CommitState::Unknown; | 431 | state = CommitState::Unknown; |
| 434 | io.flush()?; | 432 | io.flush()?; |
| 435 | write_all(io, 212, &version)?; | 433 | write_all(io, 212, &version)?; |
| ... | @@ -474,12 +472,12 @@ impl std::error::Error for CommitError { | ... | @@ -474,12 +472,12 @@ impl std::error::Error for CommitError { |
| 474 | } | 472 | } |
| 475 | } | 473 | } |
| 476 | 474 | ||
| 477 | fn write_all(io: &mut impl CommitIo, mut offset: usize, mut bytes: &[u8]) -> io::Result<()> { | 475 | fn write_all(io: &mut impl CommitIo, mut offset: u64, mut bytes: &[u8]) -> io::Result<()> { |
| 478 | while !bytes.is_empty() { | 476 | while !bytes.is_empty() { |
| 479 | match io.write_at(offset as u64, bytes) { | 477 | match io.write_at(offset, bytes) { |
| 480 | Ok(0) => return Err(io::Error::from(ErrorKind::WriteZero)), | 478 | Ok(0) => return Err(io::Error::from(ErrorKind::WriteZero)), |
| 481 | Ok(count) if count <= bytes.len() => { | 479 | Ok(count) if count <= bytes.len() => { |
| 482 | offset += count; | 480 | offset += count as u64; |
| 483 | bytes = &bytes[count..]; | 481 | bytes = &bytes[count..]; |
| 484 | } | 482 | } |
| 485 | Ok(_) => { | 483 | Ok(_) => { |
| ... | @@ -512,85 +510,203 @@ pub fn commit_property_bytes( | ... | @@ -512,85 +510,203 @@ pub fn commit_property_bytes( |
| 512 | error: io::Error::new(ErrorKind::InvalidData, error), | 510 | error: io::Error::new(ErrorKind::InvalidData, error), |
| 513 | } | 511 | } |
| 514 | })?; | 512 | })?; |
| 515 | commit_bytes(io, source, &written) | 513 | Transaction::between(source, &written).commit(io) |
| 516 | } | 514 | } |
| 517 | 515 | ||
| 518 | fn compare_snapshot(io: &mut impl CommitIo, source: &[u8]) -> io::Result<()> { | 516 | /// What a commit requires unchanged since its snapshot: the header, which every committed |
| 519 | let capacity = source.len().clamp(1, 1024 * 1024); | 517 | /// transaction and every placement rewrites (MS-ONESTORE 2.3.1 `guidFileVersion`), and the |
| 520 | let mut buffer = Vec::new(); | 518 | /// length appends start from. Equal stamps name the same committed image. |
| 521 | buffer | 519 | #[derive(Clone, Debug, PartialEq, Eq)] |
| 522 | .try_reserve_exact(capacity) | 520 | pub struct Stamp { |
| 523 | .map_err(io::Error::other)?; | 521 | pub header: [u8; 1024], |
| 524 | buffer.resize(capacity, 0); | 522 | pub length: u64, |
| 525 | let mut offset = 0; | 523 | } |
| 526 | while offset < source.len() { | 524 | |
| 527 | let size = buffer.len().min(source.len() - offset); | 525 | impl Stamp { |
| 528 | let count = match io.read_at(offset as u64, &mut buffer[..size]) { | 526 | pub fn of(image: &[u8]) -> Result<Self, crate::Error> { |
| 529 | Err(error) if error.kind() == ErrorKind::Interrupted => continue, | 527 | Ok(Self { |
| 530 | result => result?, | 528 | header: image.first_chunk().copied().ok_or(crate::Error { |
| 531 | }; | 529 | offset: 0, |
| 532 | if count == 0 { | 530 | message: "Truncated revision-store header", |
| 533 | return Err(io::Error::from(ErrorKind::UnexpectedEof)); | 531 | })?, |
| 532 | length: image.len() as u64, | ||
| 533 | }) | ||
| 534 | } | ||
| 535 | |||
| 536 | /// Reads the header and probes the length, without reading the body. | ||
| 537 | fn check(&self, io: &mut impl CommitIo) -> io::Result<()> { | ||
| 538 | let mut header = [0; 1024]; | ||
| 539 | crate::snapshot::read_exact( | ||
| 540 | &mut |offset, output| io.read_at(offset, output), | ||
| 541 | 0, | ||
| 542 | &mut header, | ||
| 543 | )?; | ||
| 544 | if header != self.header { | ||
| 545 | return Err(io::Error::new( | ||
| 546 | ErrorKind::ResourceBusy, | ||
| 547 | "The file header changed after the edit snapshot", | ||
| 548 | )); | ||
| 534 | } | 549 | } |
| 535 | if count > size || buffer[..count] != source[offset..offset + count] { | 550 | let last = self.length.checked_sub(1).ok_or(ErrorKind::InvalidInput)?; |
| 551 | let mut tail = [0; 2]; | ||
| 552 | let count = loop { | ||
| 553 | match io.read_at(last, &mut tail) { | ||
| 554 | Err(error) if error.kind() == ErrorKind::Interrupted => {} | ||
| 555 | result => break result?, | ||
| 556 | } | ||
| 557 | }; | ||
| 558 | if count != 1 { | ||
| 536 | return Err(io::Error::new( | 559 | return Err(io::Error::new( |
| 537 | ErrorKind::ResourceBusy, | 560 | ErrorKind::ResourceBusy, |
| 538 | "The locked file differs from the edit snapshot", | 561 | "The file length changed after the edit snapshot", |
| 539 | )); | 562 | )); |
| 540 | } | 563 | } |
| 541 | offset += count; | 564 | Ok(()) |
| 542 | } | ||
| 543 | if io.read_at(source.len() as u64, &mut buffer[..1])? != 0 { | ||
| 544 | return Err(io::Error::new( | ||
| 545 | ErrorKind::ResourceBusy, | ||
| 546 | "The locked file grew after the edit snapshot", | ||
| 547 | )); | ||
| 548 | } | 565 | } |
| 549 | Ok(()) | ||
| 550 | } | 566 | } |
| 551 | 567 | ||
| 552 | pub(crate) fn commit_bytes( | 568 | /// One change to a revision store as the bytes committing it writes: `append` at the base |
| 553 | io: &mut impl CommitIo, | 569 | /// length, `patches` inside the base's data area (list tails and the transaction log), and |
| 554 | source: &[u8], | 570 | /// the header that publishes them. |
| 555 | written: &[u8], | 571 | #[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)] |
| 556 | ) -> Result<(), CommitError> { | 572 | #[serde(into = "Wire", try_from = "Wire")] |
| 557 | let mut state = CommitState::NotCommitted; | 573 | pub struct Transaction { |
| 558 | let result = (|| -> io::Result<()> { | 574 | pub(crate) base: Stamp, |
| 559 | compare_snapshot(io, source)?; | 575 | pub(crate) append: Vec<u8>, |
| 560 | if written == source { | 576 | pub(crate) patches: Vec<(u64, Vec<u8>)>, |
| 561 | state = CommitState::Unknown; | 577 | pub(crate) header: [u8; 1024], |
| 562 | io.flush()?; | 578 | } |
| 563 | return Ok(()); | 579 | |
| 580 | /// A transaction as serialized: headers as byte strings, since serde stops at 32-byte arrays. | ||
| 581 | #[derive(serde::Serialize, serde::Deserialize)] | ||
| 582 | struct Wire { | ||
| 583 | base: Vec<u8>, | ||
| 584 | length: u64, | ||
| 585 | append: Vec<u8>, | ||
| 586 | patches: Vec<(u64, Vec<u8>)>, | ||
| 587 | header: Vec<u8>, | ||
| 588 | } | ||
| 589 | |||
| 590 | impl From<Transaction> for Wire { | ||
| 591 | fn from(transaction: Transaction) -> Self { | ||
| 592 | Self { | ||
| 593 | base: transaction.base.header.to_vec(), | ||
| 594 | length: transaction.base.length, | ||
| 595 | append: transaction.append, | ||
| 596 | patches: transaction.patches, | ||
| 597 | header: transaction.header.to_vec(), | ||
| 564 | } | 598 | } |
| 565 | write_all(io, source.len(), &written[source.len()..])?; | 599 | } |
| 600 | } | ||
| 601 | |||
| 602 | impl TryFrom<Wire> for Transaction { | ||
| 603 | type Error = &'static str; | ||
| 604 | |||
| 605 | fn try_from(wire: Wire) -> Result<Self, Self::Error> { | ||
| 606 | let header = |bytes: Vec<u8>| { | ||
| 607 | <[u8; 1024]>::try_from(bytes).map_err(|_| "A transaction header is 1024 bytes") | ||
| 608 | }; | ||
| 609 | Ok(Self { | ||
| 610 | base: Stamp { | ||
| 611 | header: header(wire.base)?, | ||
| 612 | length: wire.length, | ||
| 613 | }, | ||
| 614 | append: wire.append, | ||
| 615 | patches: wire.patches, | ||
| 616 | header: header(wire.header)?, | ||
| 617 | }) | ||
| 618 | } | ||
| 619 | } | ||
| 620 | |||
| 621 | impl Transaction { | ||
| 622 | /// The transaction turning `source`, a parsed image, into `written`, which extends it. | ||
| 623 | pub(crate) fn between(source: &[u8], written: &[u8]) -> Self { | ||
| 624 | let differs = |at: &usize| source[*at] != written[*at]; | ||
| 625 | let mut patches = Vec::new(); | ||
| 566 | let mut offset = 1024; | 626 | let mut offset = 1024; |
| 567 | while offset < source.len() { | 627 | while let Some(start) = (offset..source.len()).find(differs) { |
| 568 | if source[offset] == written[offset] { | 628 | let mut end = start + 1; |
| 569 | offset += 1; | 629 | // Equal gaps shorter than a write request's overhead join the patch. |
| 570 | continue; | 630 | while let Some(next) = (end..source.len().min(end + 64)).find(differs) { |
| 631 | end = next + 1; | ||
| 571 | } | 632 | } |
| 572 | let start = offset; | 633 | patches.push((start as u64, written[start..end].to_vec())); |
| 573 | while offset < source.len() && source[offset] != written[offset] { | 634 | offset = end; |
| 574 | offset += 1; | ||
| 575 | } | ||
| 576 | write_all(io, start, &written[start..offset])?; | ||
| 577 | } | 635 | } |
| 578 | io.flush()?; | 636 | Self { |
| 579 | write_all(io, 100, &written[100..212])?; | 637 | base: Stamp { |
| 580 | write_all(io, 252, &written[252..1024])?; | 638 | header: *source.first_chunk().unwrap(), |
| 581 | io.flush()?; | 639 | length: source.len() as u64, |
| 582 | let highest = (96..100).rfind(|at| source[*at] != written[*at]).unwrap(); | 640 | }, |
| 583 | state = CommitState::Unknown; | 641 | append: written[source.len()..].to_vec(), |
| 584 | write_all(io, highest, &written[highest..highest + 1])?; | 642 | patches, |
| 585 | io.flush()?; | 643 | header: *written.first_chunk().unwrap(), |
| 586 | if highest > 96 { | ||
| 587 | write_all(io, 96, &written[96..highest])?; | ||
| 588 | io.flush()?; | ||
| 589 | } | 644 | } |
| 590 | // Native readers cache the version GUID without rechecking the transaction count. | 645 | } |
| 591 | write_all(io, 212, &written[212..252])?; | 646 | |
| 592 | io.flush()?; | 647 | /// Writes this transaction into its base image, as a successful commit leaves the file. |
| 648 | pub fn apply(&self, image: &mut Vec<u8>) -> Result<(), crate::Error> { | ||
| 649 | if Stamp::of(image)? != self.base { | ||
| 650 | return Err(crate::Error { | ||
| 651 | offset: 0, | ||
| 652 | message: "The image is not this transaction's base", | ||
| 653 | }); | ||
| 654 | } | ||
| 655 | image.extend_from_slice(&self.append); | ||
| 656 | for (offset, bytes) in &self.patches { | ||
| 657 | let offset = *offset as usize; | ||
| 658 | image[offset..offset + bytes.len()].copy_from_slice(bytes); | ||
| 659 | } | ||
| 660 | image[..1024].copy_from_slice(&self.header); | ||
| 593 | Ok(()) | 661 | Ok(()) |
| 594 | })(); | 662 | } |
| 595 | result.map_err(|error| CommitError { state, error }) | 663 | |
| 664 | /// Publishes under caller-held OneNote-compatible exclusion, provided the file still has | ||
| 665 | /// the base stamp; otherwise returns ResourceBusy without writing. Appended data and | ||
| 666 | /// patches are flushed before the header, and the transaction count commits them | ||
| 667 | /// (MS-ONESTORE 2.3.3). Flush must make preceding writes durable before later ones. | ||
| 668 | pub fn commit(&self, io: &mut impl CommitIo) -> Result<(), CommitError> { | ||
| 669 | let mut state = CommitState::NotCommitted; | ||
| 670 | let result = (|| -> io::Result<()> { | ||
| 671 | self.base.check(io)?; | ||
| 672 | if self.append.is_empty() && self.patches.is_empty() && self.header == self.base.header | ||
| 673 | { | ||
| 674 | state = CommitState::Unknown; | ||
| 675 | return io.flush(); | ||
| 676 | } | ||
| 677 | write_all(io, self.base.length, &self.append)?; | ||
| 678 | for (offset, bytes) in &self.patches { | ||
| 679 | write_all(io, *offset, bytes)?; | ||
| 680 | } | ||
| 681 | io.flush()?; | ||
| 682 | write_all(io, 100, &self.header[100..212])?; | ||
| 683 | write_all(io, 252, &self.header[252..])?; | ||
| 684 | io.flush()?; | ||
| 685 | state = CommitState::Unknown; | ||
| 686 | // At a counter carry the highest changed byte commits; the lower ones follow. | ||
| 687 | if let Some(highest) = (96..100).rfind(|at| self.base.header[*at] != self.header[*at]) { | ||
| 688 | write_all(io, highest as u64, &self.header[highest..highest + 1])?; | ||
| 689 | io.flush()?; | ||
| 690 | if highest > 96 { | ||
| 691 | write_all(io, 96, &self.header[96..highest])?; | ||
| 692 | io.flush()?; | ||
| 693 | } | ||
| 694 | } | ||
| 695 | // Native readers cache the version GUID without rechecking the transaction count. | ||
| 696 | write_all(io, 212, &self.header[212..252])?; | ||
| 697 | io.flush() | ||
| 698 | })(); | ||
| 699 | result.map_err(|error| CommitError { state, error }) | ||
| 700 | } | ||
| 701 | |||
| 702 | /// `commit` under the conservative filesystem adapter's whole-file exclusion. | ||
| 703 | #[cfg(any(unix, windows))] | ||
| 704 | pub fn commit_file(&self, path: impl AsRef<Path>) -> Result<(), CommitError> { | ||
| 705 | let mut io = FileIo::open(path, true).map_err(|error| CommitError { | ||
| 706 | state: CommitState::NotCommitted, | ||
| 707 | error, | ||
| 708 | })?; | ||
| 709 | let result = self.commit(&mut io); | ||
| 710 | io.finish(result) | ||
| 711 | } | ||
| 596 | } | 712 | } |
crates/onestore/src/create.rs+56-13| ... | @@ -1,5 +1,6 @@ | ... | @@ -1,5 +1,6 @@ |
| 1 | use crate::{ | 1 | use crate::{ |
| 2 | Chunk, Error, ExGuid, FileType, PropertySets, Reference, RevisionIndex, Store, | 2 | Chunk, Error, ExGuid, FileType, PropertySets, Reference, RevisionIndex, Store, |
| 3 | op::content::{NATIVE_INDENTS, measurement_bytes}, | ||
| 3 | store::crc, | 4 | store::crc, |
| 4 | write::{append, append_list, fresh_guid, node}, | 5 | write::{append, append_list, fresh_guid, node}, |
| 5 | }; | 6 | }; |
| ... | @@ -24,6 +25,20 @@ pub(crate) fn default_text_style() -> Vec<(u32, Vec<u8>)> { | ... | @@ -24,6 +25,20 @@ pub(crate) fn default_text_style() -> Vec<(u32, Vec<u8>)> { |
| 24 | ] | 25 | ] |
| 25 | } | 26 | } |
| 26 | 27 | ||
| 28 | /// The page margins MS-ONE requires on every page, in half inches, as OneNote 2010 sets them | ||
| 29 | /// on the pages it creates. | ||
| 30 | pub(crate) fn page_margins() -> Vec<(u32, Vec<u8>)> { | ||
| 31 | [ | ||
| 32 | (0x14001c4c, 1.0_f32), | ||
| 33 | (0x14001c4d, 1.0), | ||
| 34 | (0x14001c4e, 2.0), | ||
| 35 | (0x14001c4f, 2.0), | ||
| 36 | ] | ||
| 37 | .into_iter() | ||
| 38 | .map(|(id, value)| (id, value.to_le_bytes().to_vec())) | ||
| 39 | .collect() | ||
| 40 | } | ||
| 41 | |||
| 27 | pub(crate) fn properties(values: &[(u32, Vec<u8>)]) -> Result<Vec<u8>> { | 42 | pub(crate) fn properties(values: &[(u32, Vec<u8>)]) -> Result<Vec<u8>> { |
| 28 | let mut streams: [Vec<u8>; 3] = std::array::from_fn(|_| Vec::new()); | 43 | let mut streams: [Vec<u8>; 3] = std::array::from_fn(|_| Vec::new()); |
| 29 | let mut fields = Vec::new(); | 44 | let mut fields = Vec::new(); |
| ... | @@ -110,14 +125,38 @@ pub(crate) fn placement(ancestor: [u8; 16], name: &str) -> [u8; 20] { | ... | @@ -110,14 +125,38 @@ pub(crate) fn placement(ancestor: [u8; 16], name: &str) -> [u8; 20] { |
| 110 | bytes | 125 | bytes |
| 111 | } | 126 | } |
| 112 | 127 | ||
| 128 | thread_local! { | ||
| 129 | /// The FILETIME `at` gives writers in place of the system clock. | ||
| 130 | static CLOCK: std::cell::Cell<Option<u64>> = const { std::cell::Cell::new(None) }; | ||
| 131 | } | ||
| 132 | |||
| 133 | /// Runs `f` with the writers' clock reading `filetime`, as an op's modification time. | ||
| 134 | pub(crate) fn at<T>(filetime: u64, f: impl FnOnce() -> T) -> T { | ||
| 135 | struct Restore(Option<u64>); | ||
| 136 | impl Drop for Restore { | ||
| 137 | fn drop(&mut self) { | ||
| 138 | CLOCK.set(self.0); | ||
| 139 | } | ||
| 140 | } | ||
| 141 | let _restore = Restore(CLOCK.replace(Some(filetime))); | ||
| 142 | f() | ||
| 143 | } | ||
| 144 | |||
| 145 | /// Seconds since 1980 as OneNote's Time32, and FILETIME, of the writers' clock. | ||
| 113 | pub(crate) fn current_timestamps() -> Result<(u32, u64)> { | 146 | pub(crate) fn current_timestamps() -> Result<(u32, u64)> { |
| 114 | let now = SystemTime::now() | 147 | let now = match CLOCK.get() { |
| 115 | .duration_since(UNIX_EPOCH) | 148 | Some(filetime) => (filetime / 10_000_000).checked_sub(11644473600).ok_or(Error { |
| 116 | .map_err(|_| Error { | ||
| 117 | offset: 0, | 149 | offset: 0, |
| 118 | message: "System time precedes the Unix epoch", | 150 | message: "An edit time precedes the Unix epoch", |
| 119 | })? | 151 | })?, |
| 120 | .as_secs(); | 152 | None => SystemTime::now() |
| 153 | .duration_since(UNIX_EPOCH) | ||
| 154 | .map_err(|_| Error { | ||
| 155 | offset: 0, | ||
| 156 | message: "System time precedes the Unix epoch", | ||
| 157 | })? | ||
| 158 | .as_secs(), | ||
| 159 | }; | ||
| 121 | let modified = now | 160 | let modified = now |
| 122 | .checked_sub(315532800) | 161 | .checked_sub(315532800) |
| 123 | .and_then(|time| u32::try_from(time).ok()) | 162 | .and_then(|time| u32::try_from(time).ok()) |
| ... | @@ -205,12 +244,16 @@ pub fn create_section(file_name: &str, text: &str, author: &str) -> Result<Vec<u | ... | @@ -205,12 +244,16 @@ pub fn create_section(file_name: &str, text: &str, author: &str) -> Result<Vec<u |
| 205 | NewObject { | 244 | NewObject { |
| 206 | id: 22, | 245 | id: 22, |
| 207 | jcid: 0x6000b, | 246 | jcid: 0x6000b, |
| 208 | properties: vec![ | 247 | properties: [ |
| 209 | last_modified(), | 248 | vec![ |
| 210 | (0x24001c20, id(23)), | 249 | last_modified(), |
| 211 | (0x1c001d75, string(author)), | 250 | (0x24001c20, id(23)), |
| 212 | (0x1c001d3c, string(crate::edit::automatic_title(text))), | 251 | (0x1c001d75, string(author)), |
| 213 | ], | 252 | (0x1c001d3c, string(crate::edit::automatic_title(text))), |
| 253 | ], | ||
| 254 | page_margins(), | ||
| 255 | ] | ||
| 256 | .concat(), | ||
| 214 | }, | 257 | }, |
| 215 | NewObject { | 258 | NewObject { |
| 216 | id: 23, | 259 | id: 23, |
| ... | @@ -219,7 +262,7 @@ pub fn create_section(file_name: &str, text: &str, author: &str) -> Result<Vec<u | ... | @@ -219,7 +262,7 @@ pub fn create_section(file_name: &str, text: &str, author: &str) -> Result<Vec<u |
| 219 | last_modified(), | 262 | last_modified(), |
| 220 | (0x24001c20, id(24)), | 263 | (0x24001c20, id(24)), |
| 221 | (0x0c001c03, vec![1]), | 264 | (0x0c001c03, vec![1]), |
| 222 | (0x1c001c12, vec![1, 0, 0, 0, 0, 0, 0, 0]), | 265 | (0x1c001c12, measurement_bytes(&NATIVE_INDENTS, 4)?), |
| 223 | (0x14001c14, 1_f32.to_le_bytes().to_vec()), | 266 | (0x14001c14, 1_f32.to_le_bytes().to_vec()), |
| 224 | (0x14001c15, 1_f32.to_le_bytes().to_vec()), | 267 | (0x14001c15, 1_f32.to_le_bytes().to_vec()), |
| 225 | (0x14001c1b, 13_f32.to_le_bytes().to_vec()), | 268 | (0x14001c1b, 13_f32.to_le_bytes().to_vec()), |
crates/onestore/src/document.rs+110-88| ... | @@ -5,7 +5,7 @@ | ... | @@ -5,7 +5,7 @@ |
| 5 | 5 | ||
| 6 | use crate::{ | 6 | use crate::{ |
| 7 | Error, ExGuid, FileDataReference, IdStream, Object, ObjectData, Property, PropertySets, | 7 | Error, ExGuid, FileDataReference, IdStream, Object, ObjectData, Property, PropertySets, |
| 8 | RevisionIndex, Store, Value, bytes::Cursor, | 8 | RevisionIndex, Value, bytes::Cursor, |
| 9 | }; | 9 | }; |
| 10 | use serde::{Deserialize, Serialize}; | 10 | use serde::{Deserialize, Serialize}; |
| 11 | use std::collections::{BTreeMap, BTreeSet}; | 11 | use std::collections::{BTreeMap, BTreeSet}; |
| ... | @@ -98,6 +98,13 @@ macro_rules! format_fields { | ... | @@ -98,6 +98,13 @@ macro_rules! format_fields { |
| 98 | pub fn inherit(&self, parent: &Self) -> Self { | 98 | pub fn inherit(&self, parent: &Self) -> Self { |
| 99 | Self { $($field: self.$field.as_ref().or(parent.$field.as_ref()).cloned()),* } | 99 | Self { $($field: self.$field.as_ref().or(parent.$field.as_ref()).cloned()),* } |
| 100 | } | 100 | } |
| 101 | |||
| 102 | /// The values `self` sets that `parent` does not already give. | ||
| 103 | pub fn over(&self, parent: &Self) -> Self { | ||
| 104 | Self { | ||
| 105 | $($field: self.$field.as_ref().filter(|value| parent.$field.as_ref() != Some(*value)).cloned()),* | ||
| 106 | } | ||
| 107 | } | ||
| 101 | } | 108 | } |
| 102 | }; | 109 | }; |
| 103 | } | 110 | } |
| ... | @@ -275,6 +282,98 @@ impl Revision<'_> { | ... | @@ -275,6 +282,98 @@ impl Revision<'_> { |
| 275 | } | 282 | } |
| 276 | } | 283 | } |
| 277 | 284 | ||
| 285 | impl<'a> Revision<'a> { | ||
| 286 | /// The elements reachable in `revision` of `space`, and the (space, context) pairs they | ||
| 287 | /// reference. | ||
| 288 | pub(crate) fn parse( | ||
| 289 | space: ExGuid, | ||
| 290 | revision: &crate::ResolvedRevision<'a>, | ||
| 291 | file_type: crate::FileType, | ||
| 292 | file_data: &mut impl FnMut([u8; 16]) -> Result<&'a [u8]>, | ||
| 293 | ) -> Result<(Self, Vec<(ExGuid, ExGuid)>)> { | ||
| 294 | let mut reachable = BTreeSet::new(); | ||
| 295 | let mut objects: Vec<_> = revision.roots.values().copied().collect(); | ||
| 296 | let mut encrypted = false; | ||
| 297 | while let Some(oid) = objects.pop() { | ||
| 298 | if !reachable.insert(oid) { | ||
| 299 | continue; | ||
| 300 | } | ||
| 301 | let object = revision | ||
| 302 | .objects | ||
| 303 | .get(&oid) | ||
| 304 | .ok_or_else(|| invalid("Document object has no declaration"))?; | ||
| 305 | if matches!(object.data, ObjectData::Encrypted(_)) { | ||
| 306 | encrypted = true; | ||
| 307 | } else { | ||
| 308 | objects.extend(object.references()?.objects); | ||
| 309 | } | ||
| 310 | } | ||
| 311 | if !encrypted { | ||
| 312 | revision.reachable()?; | ||
| 313 | } | ||
| 314 | let mut referenced = Vec::new(); | ||
| 315 | let mut nodes = BTreeMap::new(); | ||
| 316 | for oid in reachable { | ||
| 317 | let object = &revision.objects[&oid]; | ||
| 318 | if !matches!(object.data, ObjectData::Encrypted(_)) { | ||
| 319 | let refs = object.references()?; | ||
| 320 | referenced.extend( | ||
| 321 | refs.object_spaces | ||
| 322 | .into_iter() | ||
| 323 | .map(|sid| (sid, ExGuid::default())), | ||
| 324 | ); | ||
| 325 | referenced.extend(refs.contexts.into_iter().map(|context| (space, context))); | ||
| 326 | } | ||
| 327 | nodes.insert(oid, Element::parse_with(object, file_type, file_data)?); | ||
| 328 | } | ||
| 329 | for node in nodes.values() { | ||
| 330 | if let Kind::RichText { | ||
| 331 | runs, | ||
| 332 | paragraph_style, | ||
| 333 | .. | ||
| 334 | } = &node.kind | ||
| 335 | { | ||
| 336 | for target in paragraph_style | ||
| 337 | .iter() | ||
| 338 | .chain(runs.iter().filter_map(|r| r.format.as_ref())) | ||
| 339 | { | ||
| 340 | if !matches!(nodes.get(target).map(|n| &n.kind), Some(Kind::Style { .. })) { | ||
| 341 | return Err(invalid("Text formatting does not reference a style")); | ||
| 342 | } | ||
| 343 | } | ||
| 344 | } | ||
| 345 | let mut tag_types = BTreeSet::new(); | ||
| 346 | for tag in &node.tags { | ||
| 347 | let action_type = if tag.status & 4 != 0 { | ||
| 348 | tag.action_type | ||
| 349 | } else { | ||
| 350 | match tag | ||
| 351 | .definition | ||
| 352 | .and_then(|id| nodes.get(&id)) | ||
| 353 | .map(|n| &n.kind) | ||
| 354 | { | ||
| 355 | Some(Kind::TagDefinition { action_type, .. }) => *action_type, | ||
| 356 | _ => { | ||
| 357 | return Err(invalid("Note tag does not reference a tag definition")); | ||
| 358 | } | ||
| 359 | } | ||
| 360 | } | ||
| 361 | .ok_or_else(|| invalid("Note tag has no action type"))?; | ||
| 362 | if !tag_types.insert(action_type) { | ||
| 363 | return Err(invalid("Repeated note tag action type")); | ||
| 364 | } | ||
| 365 | } | ||
| 366 | } | ||
| 367 | Ok(( | ||
| 368 | Self { | ||
| 369 | roots: revision.roots.clone(), | ||
| 370 | nodes, | ||
| 371 | }, | ||
| 372 | referenced, | ||
| 373 | )) | ||
| 374 | } | ||
| 375 | } | ||
| 376 | |||
| 278 | impl Revision<'_> { | 377 | impl Revision<'_> { |
| 279 | /// Maps every element reachable from `roots` through children, content and structure | 378 | /// Maps every element reachable from `roots` through children, content and structure |
| 280 | /// references to its referencing parents, in visiting order. | 379 | /// references to its referencing parents, in visiting order. |
| ... | @@ -807,91 +906,14 @@ impl<'a> Document<'a> { | ... | @@ -807,91 +906,14 @@ impl<'a> Document<'a> { |
| 807 | if space.revisions.contains_key(&rid) { | 906 | if space.revisions.contains_key(&rid) { |
| 808 | continue; | 907 | continue; |
| 809 | } | 908 | } |
| 810 | let revision = resolve(id, rid)?; | 909 | let (revision, referenced) = Revision::parse( |
| 811 | let mut reachable = BTreeSet::new(); | 910 | id, |
| 812 | let mut objects: Vec<_> = revision.roots.values().copied().collect(); | 911 | &resolve(id, rid)?, |
| 813 | let mut encrypted = false; | 912 | index.store.header.file_type, |
| 814 | while let Some(oid) = objects.pop() { | 913 | &mut file_data, |
| 815 | if !reachable.insert(oid) { | 914 | )?; |
| 816 | continue; | 915 | pending.extend(referenced); |
| 817 | } | 916 | space.revisions.insert(rid, revision); |
| 818 | let object = revision | ||
| 819 | .objects | ||
| 820 | .get(&oid) | ||
| 821 | .ok_or_else(|| invalid("Document object has no declaration"))?; | ||
| 822 | if matches!(object.data, ObjectData::Encrypted(_)) { | ||
| 823 | encrypted = true; | ||
| 824 | } else { | ||
| 825 | objects.extend(object.references()?.objects); | ||
| 826 | } | ||
| 827 | } | ||
| 828 | if !encrypted { | ||
| 829 | revision.reachable()?; | ||
| 830 | } | ||
| 831 | let mut nodes = BTreeMap::new(); | ||
| 832 | for oid in reachable { | ||
| 833 | let object = &revision.objects[&oid]; | ||
| 834 | if !matches!(object.data, ObjectData::Encrypted(_)) { | ||
| 835 | let refs = object.references()?; | ||
| 836 | pending.extend( | ||
| 837 | refs.object_spaces | ||
| 838 | .into_iter() | ||
| 839 | .map(|sid| (sid, ExGuid::default())), | ||
| 840 | ); | ||
| 841 | pending.extend(refs.contexts.into_iter().map(|context| (id, context))); | ||
| 842 | } | ||
| 843 | nodes.insert( | ||
| 844 | oid, | ||
| 845 | Element::parse_with(object, index.store, &mut file_data)?, | ||
| 846 | ); | ||
| 847 | } | ||
| 848 | for node in nodes.values() { | ||
| 849 | if let Kind::RichText { | ||
| 850 | runs, | ||
| 851 | paragraph_style, | ||
| 852 | .. | ||
| 853 | } = &node.kind | ||
| 854 | { | ||
| 855 | for target in paragraph_style | ||
| 856 | .iter() | ||
| 857 | .chain(runs.iter().filter_map(|r| r.format.as_ref())) | ||
| 858 | { | ||
| 859 | if !matches!(nodes.get(target).map(|n| &n.kind), Some(Kind::Style { .. })) { | ||
| 860 | return Err(invalid("Text formatting does not reference a style")); | ||
| 861 | } | ||
| 862 | } | ||
| 863 | } | ||
| 864 | let mut tag_types = BTreeSet::new(); | ||
| 865 | for tag in &node.tags { | ||
| 866 | let action_type = if tag.status & 4 != 0 { | ||
| 867 | tag.action_type | ||
| 868 | } else { | ||
| 869 | match tag | ||
| 870 | .definition | ||
| 871 | .and_then(|id| nodes.get(&id)) | ||
| 872 | .map(|n| &n.kind) | ||
| 873 | { | ||
| 874 | Some(Kind::TagDefinition { action_type, .. }) => *action_type, | ||
| 875 | _ => { | ||
| 876 | return Err(invalid( | ||
| 877 | "Note tag does not reference a tag definition", | ||
| 878 | )); | ||
| 879 | } | ||
| 880 | } | ||
| 881 | } | ||
| 882 | .ok_or_else(|| invalid("Note tag has no action type"))?; | ||
| 883 | if !tag_types.insert(action_type) { | ||
| 884 | return Err(invalid("Repeated note tag action type")); | ||
| 885 | } | ||
| 886 | } | ||
| 887 | } | ||
| 888 | space.revisions.insert( | ||
| 889 | rid, | ||
| 890 | Revision { | ||
| 891 | roots: revision.roots, | ||
| 892 | nodes, | ||
| 893 | }, | ||
| 894 | ); | ||
| 895 | } | 917 | } |
| 896 | for space in spaces.values() { | 918 | for space in spaces.values() { |
| 897 | for node in space.revisions.values().flat_map(|r| r.nodes.values()) { | 919 | for node in space.revisions.values().flat_map(|r| r.nodes.values()) { |
| ... | @@ -931,7 +953,7 @@ impl<'a> Document<'a> { | ... | @@ -931,7 +953,7 @@ impl<'a> Document<'a> { |
| 931 | impl<'a> Element<'a> { | 953 | impl<'a> Element<'a> { |
| 932 | pub(crate) fn parse_with( | 954 | pub(crate) fn parse_with( |
| 933 | object: &Object<'a>, | 955 | object: &Object<'a>, |
| 934 | store: &Store<'a>, | 956 | file_type: crate::FileType, |
| 935 | file_data: &mut impl FnMut([u8; 16]) -> Result<&'a [u8]>, | 957 | file_data: &mut impl FnMut([u8; 16]) -> Result<&'a [u8]>, |
| 936 | ) -> Result<Self> { | 958 | ) -> Result<Self> { |
| 937 | let empty = |kind| Self { | 959 | let empty = |kind| Self { |
| ... | @@ -1311,7 +1333,7 @@ impl<'a> Element<'a> { | ... | @@ -1311,7 +1333,7 @@ impl<'a> Element<'a> { |
| 1311 | color: f.u32(0x14003466)?, | 1333 | color: f.u32(0x14003466)?, |
| 1312 | highlight: f.u32(0x14003465)?, | 1334 | highlight: f.u32(0x14003465)?, |
| 1313 | }, | 1335 | }, |
| 1314 | 0x20001 if store.header.file_type == crate::FileType::TableOfContents => Kind::Toc { | 1336 | 0x20001 if file_type == crate::FileType::TableOfContents => Kind::Toc { |
| 1315 | entries: f.refs(0x24001cf6, IdStream::Objects)?, | 1337 | entries: f.refs(0x24001cf6, IdStream::Objects)?, |
| 1316 | filename: f.text(0x1c001d6b)?, | 1338 | filename: f.text(0x1c001d6b)?, |
| 1317 | identity: f.fixed(0x1c001d94)?, | 1339 | identity: f.fixed(0x1c001d94)?, |
crates/onestore/src/files.rs+42-37| ... | @@ -131,42 +131,47 @@ impl<'a> Store<'a> { | ... | @@ -131,42 +131,47 @@ impl<'a> Store<'a> { |
| 131 | offset: 0, | 131 | offset: 0, |
| 132 | message: "File-data object is not declared", | 132 | message: "File-data object is not declared", |
| 133 | })?; | 133 | })?; |
| 134 | let mut c = Cursor { | 134 | payload( |
| 135 | bytes: self.chunk_data(chunk)?, | 135 | self.chunk_data(chunk)?, |
| 136 | offset: usize::try_from(chunk.offset).unwrap(), | 136 | usize::try_from(chunk.offset).unwrap(), |
| 137 | }; | 137 | ) |
| 138 | if c.read::<16>()? | 138 | } |
| 139 | != [ | 139 | } |
| 140 | 0xe7, 0x16, 0xe3, 0xbd, 0x65, 0x26, 0x11, 0x45, 0xa4, 0xc4, 0x8d, 0x4d, 0x0b, 0x7a, | 140 | |
| 141 | 0x9e, 0xac, | 141 | /// The payload of a file-data store object, whose bytes start at file offset `offset`. |
| 142 | ] | 142 | pub(crate) fn payload(bytes: &[u8], offset: usize) -> Result<&[u8], Error> { |
| 143 | { | 143 | let mut c = Cursor { bytes, offset }; |
| 144 | return Err(Error { | 144 | if c.read::<16>()? |
| 145 | offset: c.offset - 16, | 145 | != [ |
| 146 | message: "Invalid file-data object header", | 146 | 0xe7, 0x16, 0xe3, 0xbd, 0x65, 0x26, 0x11, 0x45, 0xa4, 0xc4, 0x8d, 0x4d, 0x0b, 0x7a, |
| 147 | }); | 147 | 0x9e, 0xac, |
| 148 | } | 148 | ] |
| 149 | let length = usize::try_from(u64::from_le_bytes(c.read()?)).map_err(|_| Error { | 149 | { |
| 150 | offset: c.offset - 8, | 150 | return Err(Error { |
| 151 | message: "File-data length exceeds address space", | 151 | offset: c.offset - 16, |
| 152 | })?; | 152 | message: "Invalid file-data object header", |
| 153 | c.take(12)?; | 153 | }); |
| 154 | let bytes = c.take(length)?; | 154 | } |
| 155 | // The object length is aligned; its position in the file need not be. | 155 | let length = usize::try_from(u64::from_le_bytes(c.read()?)).map_err(|_| Error { |
| 156 | let padding = (8 - (36 + length) % 8) % 8; | 156 | offset: c.offset - 8, |
| 157 | c.take(padding)?; | 157 | message: "File-data length exceeds address space", |
| 158 | if c.read::<16>()? | 158 | })?; |
| 159 | != [ | 159 | c.take(12)?; |
| 160 | 0x22, 0xa7, 0xfb, 0x71, 0x79, 0x0f, 0x0b, 0x4a, 0xbb, 0x13, 0x89, 0x92, 0x56, 0x42, | 160 | let bytes = c.take(length)?; |
| 161 | 0x6b, 0x24, | 161 | // The object length is aligned; its position in the file need not be. |
| 162 | ] | 162 | let padding = (8 - (36 + length) % 8) % 8; |
| 163 | || !c.bytes.is_empty() | 163 | c.take(padding)?; |
| 164 | { | 164 | if c.read::<16>()? |
| 165 | return Err(Error { | 165 | != [ |
| 166 | offset: c.offset - 16, | 166 | 0x22, 0xa7, 0xfb, 0x71, 0x79, 0x0f, 0x0b, 0x4a, 0xbb, 0x13, 0x89, 0x92, 0x56, 0x42, |
| 167 | message: "Invalid file-data object footer or length", | 167 | 0x6b, 0x24, |
| 168 | }); | 168 | ] |
| 169 | } | 169 | || !c.bytes.is_empty() |
| 170 | Ok(bytes) | 170 | { |
| 171 | return Err(Error { | ||
| 172 | offset: c.offset - 16, | ||
| 173 | message: "Invalid file-data object footer or length", | ||
| 174 | }); | ||
| 171 | } | 175 | } |
| 176 | Ok(bytes) | ||
| 172 | } | 177 | } |
crates/onestore/src/formatting.rs+21-5| ... | @@ -12,6 +12,9 @@ use std::{ | ... | @@ -12,6 +12,9 @@ use std::{ |
| 12 | sync::Arc, | 12 | sync::Arc, |
| 13 | }; | 13 | }; |
| 14 | 14 | ||
| 15 | /// The font sizes OneNote accepts, in points; sizes are written in half points. | ||
| 16 | pub const FONT_SIZES: std::ops::RangeInclusive<f32> = 6.0..=130.0; | ||
| 17 | |||
| 15 | fn invalid(message: &'static str) -> Error { | 18 | fn invalid(message: &'static str) -> Error { |
| 16 | Error { offset: 0, message } | 19 | Error { offset: 0, message } |
| 17 | } | 20 | } |
| ... | @@ -43,6 +46,8 @@ pub enum TextAttribute { | ... | @@ -43,6 +46,8 @@ pub enum TextAttribute { |
| 43 | Hyperlink(bool), | 46 | Hyperlink(bool), |
| 44 | /// The run is a hyperlink's visible label or field code rather than surrounding text. | 47 | /// The run is a hyperlink's visible label or field code rather than surrounding text. |
| 45 | HyperlinkLabel(bool), | 48 | HyperlinkLabel(bool), |
| 49 | /// Only false: equation text is written whole through the page model. | ||
| 50 | Math(bool), | ||
| 46 | } | 51 | } |
| 47 | 52 | ||
| 48 | impl TextAttribute { | 53 | impl TextAttribute { |
| ... | @@ -58,6 +63,10 @@ impl TextAttribute { | ... | @@ -58,6 +63,10 @@ impl TextAttribute { |
| 58 | Self::Hidden(value) => boolean(0x08001e16, *value), | 63 | Self::Hidden(value) => boolean(0x08001e16, *value), |
| 59 | Self::Hyperlink(value) => boolean(0x08001e14, *value), | 64 | Self::Hyperlink(value) => boolean(0x08001e14, *value), |
| 60 | Self::HyperlinkLabel(value) => boolean(0x08001e19, *value), | 65 | Self::HyperlinkLabel(value) => boolean(0x08001e19, *value), |
| 66 | Self::Math(true) => { | ||
| 67 | return Err(invalid("Equations are written through the page model")); | ||
| 68 | } | ||
| 69 | Self::Math(false) => boolean(0x08003401, false), | ||
| 61 | Self::Font(font) => { | 70 | Self::Font(font) => { |
| 62 | if font.is_empty() || font.contains('\0') { | 71 | if font.is_empty() || font.contains('\0') { |
| 63 | return Err(invalid("Font names must be nonempty and contain no NUL")); | 72 | return Err(invalid("Font names must be nonempty and contain no NUL")); |
| ... | @@ -66,7 +75,7 @@ impl TextAttribute { | ... | @@ -66,7 +75,7 @@ impl TextAttribute { |
| 66 | } | 75 | } |
| 67 | Self::FontSize(points) => { | 76 | Self::FontSize(points) => { |
| 68 | if !points.is_finite() | 77 | if !points.is_finite() |
| 69 | || !(6.0..=130.0).contains(points) | 78 | || !FONT_SIZES.contains(points) |
| 70 | || (points * 2.0).fract() != 0.0 | 79 | || (points * 2.0).fract() != 0.0 |
| 71 | { | 80 | { |
| 72 | return Err(invalid( | 81 | return Err(invalid( |
| ... | @@ -99,23 +108,29 @@ pub(crate) fn format_text( | ... | @@ -99,23 +108,29 @@ pub(crate) fn format_text( |
| 99 | attributes: &[TextAttribute], | 108 | attributes: &[TextAttribute], |
| 100 | ) -> Result<Vec<u8>, Error> { | 109 | ) -> Result<Vec<u8>, Error> { |
| 101 | crate::active::write(source, space, |active| { | 110 | crate::active::write(source, space, |active| { |
| 102 | format_changes(active, object, range, attributes) | 111 | format_changes(active, object, range, attributes, &[]) |
| 103 | }) | 112 | }) |
| 104 | } | 113 | } |
| 105 | 114 | ||
| 106 | /// `format_text` on an active page. | 115 | /// `format_text` on an active page, also removing the `cleared` properties from the range's |
| 116 | /// run styles so the text inherits them. | ||
| 107 | pub(crate) fn format_changes( | 117 | pub(crate) fn format_changes( |
| 108 | active: &ActivePage<'_>, | 118 | active: &ActivePage<'_>, |
| 109 | object: ExGuid, | 119 | object: ExGuid, |
| 110 | range: Range<u32>, | 120 | range: Range<u32>, |
| 111 | attributes: &[TextAttribute], | 121 | attributes: &[TextAttribute], |
| 122 | cleared: &[crate::op::TextProperty], | ||
| 112 | ) -> Result<Changes, Error> { | 123 | ) -> Result<Changes, Error> { |
| 113 | if attributes.is_empty() || range.start > range.end { | 124 | let cleared: Vec<u32> = cleared.iter().map(|property| property.id()).collect(); |
| 125 | if (attributes.is_empty() && cleared.is_empty()) || range.start > range.end { | ||
| 114 | return Err(invalid( | 126 | return Err(invalid( |
| 115 | "Select a text range and at least one formatting attribute", | 127 | "Select a text range and at least one formatting attribute", |
| 116 | )); | 128 | )); |
| 117 | } | 129 | } |
| 118 | let values = attribute_values(attributes)?; | 130 | let values = match attributes { |
| 131 | [] => Vec::new(), | ||
| 132 | attributes => attribute_values(attributes)?, | ||
| 133 | }; | ||
| 119 | let view = &active.view; | 134 | let view = &active.view; |
| 120 | let parents = active.editable_parents(object)?; | 135 | let parents = active.editable_parents(object)?; |
| 121 | let node = &view.nodes[&object]; | 136 | let node = &view.nodes[&object]; |
| ... | @@ -217,6 +232,7 @@ pub(crate) fn format_changes( | ... | @@ -217,6 +232,7 @@ pub(crate) fn format_changes( |
| 217 | }; | 232 | }; |
| 218 | if selected { | 233 | if selected { |
| 219 | style.set(&changes)?; | 234 | style.set(&changes)?; |
| 235 | style.remove(&cleared)?; | ||
| 220 | } | 236 | } |
| 221 | if let Some(id) = previous | 237 | if let Some(id) = previous |
| 222 | .filter(|id| raw.objects[id].data == crate::ObjectData::Properties(&style.bytes)) | 238 | .filter(|id| raw.objects[id].data == crate::ObjectData::Properties(&style.bytes)) |
crates/onestore/src/insertion.rs+67-32| ... | @@ -3,6 +3,7 @@ use crate::{ | ... | @@ -3,6 +3,7 @@ use crate::{ |
| 3 | active::{ActivePage, Changes}, | 3 | active::{ActivePage, Changes}, |
| 4 | create::{current_timestamps, default_text_style, properties, string}, | 4 | create::{current_timestamps, default_text_style, properties, string}, |
| 5 | document::Kind, | 5 | document::Kind, |
| 6 | op::content::{NATIVE_INDENTS, measurement_bytes}, | ||
| 6 | write::{PropertyObject, fresh_guid}, | 7 | write::{PropertyObject, fresh_guid}, |
| 7 | }; | 8 | }; |
| 8 | use serde::{Deserialize, Serialize}; | 9 | use serde::{Deserialize, Serialize}; |
| ... | @@ -195,6 +196,36 @@ impl Insertion { | ... | @@ -195,6 +196,36 @@ impl Insertion { |
| 195 | } | 196 | } |
| 196 | 197 | ||
| 197 | pub(crate) fn changes(&self, active: &ActivePage<'_>) -> Result<Changes, Error> { | 198 | pub(crate) fn changes(&self, active: &ActivePage<'_>) -> Result<Changes, Error> { |
| 199 | let paragraph = ExGuid { | ||
| 200 | guid: self.guid, | ||
| 201 | n: if matches!(self.placement, Placement::Outline { .. }) { | ||
| 202 | 3 | ||
| 203 | } else { | ||
| 204 | 1 | ||
| 205 | }, | ||
| 206 | }; | ||
| 207 | let changes = self.changes_as(active, self.object(), paragraph, self.text_object())?; | ||
| 208 | let raw = &active.live.revision; | ||
| 209 | if changes | ||
| 210 | .keys() | ||
| 211 | .any(|id| id.guid == self.guid && raw.objects.contains_key(id)) | ||
| 212 | { | ||
| 213 | return Err(invalid( | ||
| 214 | "An insertion identity is already present; reconcile the existing edit", | ||
| 215 | )); | ||
| 216 | } | ||
| 217 | Ok(changes) | ||
| 218 | } | ||
| 219 | |||
| 220 | /// `changes` creating the outline or paragraph `object`, the paragraph `paragraph` and its | ||
| 221 | /// text `text` under those identities. | ||
| 222 | pub(crate) fn changes_as( | ||
| 223 | &self, | ||
| 224 | active: &ActivePage<'_>, | ||
| 225 | object: ExGuid, | ||
| 226 | paragraph: ExGuid, | ||
| 227 | text: ExGuid, | ||
| 228 | ) -> Result<Changes, Error> { | ||
| 198 | let [page] = active.pages.as_slice() else { | 229 | let [page] = active.pages.as_slice() else { |
| 199 | return Err(invalid("Insertion requires a single active page")); | 230 | return Err(invalid("Insertion requires a single active page")); |
| 200 | }; | 231 | }; |
| ... | @@ -247,42 +278,54 @@ impl Insertion { | ... | @@ -247,42 +278,54 @@ impl Insertion { |
| 247 | pending.extend(parents.get(&id).into_iter().flatten().copied()); | 278 | pending.extend(parents.get(&id).into_iter().flatten().copied()); |
| 248 | } | 279 | } |
| 249 | let modified = current_timestamps()?.0.to_le_bytes(); | 280 | let modified = current_timestamps()?.0.to_le_bytes(); |
| 250 | let paragraph_n = if matches!(self.placement, Placement::Outline { .. }) { | 281 | let (author, default) = ( |
| 251 | 3 | 282 | ExGuid { |
| 252 | } else { | 283 | guid: self.guid, |
| 253 | 1 | 284 | n: 4, |
| 254 | }; | 285 | }, |
| 255 | let table = Arc::new(BTreeMap::from([(0, self.guid)])); | 286 | ExGuid { |
| 256 | let reference = |n: u32| n.to_le_bytes().to_vec(); | 287 | guid: self.guid, |
| 288 | n: 5, | ||
| 289 | }, | ||
| 290 | ); | ||
| 291 | let mut table = BTreeMap::from([(0, self.guid)]); | ||
| 292 | for id in [object, paragraph, text] { | ||
| 293 | if !table.values().any(|guid| *guid == id.guid) { | ||
| 294 | table.insert(table.len() as u32, id.guid); | ||
| 295 | } | ||
| 296 | } | ||
| 297 | let table = Arc::new(table); | ||
| 298 | let reference = | ||
| 299 | |id: ExGuid| crate::write::compact(id, &table).map(|compact| compact.to_vec()); | ||
| 257 | let mut new = BTreeMap::new(); | 300 | let mut new = BTreeMap::new(); |
| 258 | for (n, jcid, values) in [ | 301 | for (id, jcid, values) in [ |
| 259 | ( | 302 | ( |
| 260 | paragraph_n, | 303 | paragraph, |
| 261 | 0x6000d, | 304 | 0x6000d, |
| 262 | vec![ | 305 | vec![ |
| 263 | (0x14001d7a, modified.to_vec()), | 306 | (0x14001d7a, modified.to_vec()), |
| 264 | (0x14001d09, self.created.to_le_bytes().to_vec()), | 307 | (0x14001d09, self.created.to_le_bytes().to_vec()), |
| 265 | (0x0c001c03, vec![1]), | 308 | (0x0c001c03, vec![1]), |
| 266 | (0x24001c1f, reference(2)), | 309 | (0x24001c1f, reference(text)?), |
| 267 | (0x20001d78, reference(4)), | 310 | (0x20001d78, reference(author)?), |
| 268 | (0x20001d79, reference(4)), | 311 | (0x20001d79, reference(author)?), |
| 269 | ], | 312 | ], |
| 270 | ), | 313 | ), |
| 271 | ( | 314 | ( |
| 272 | 2, | 315 | text, |
| 273 | 0x6000e, | 316 | 0x6000e, |
| 274 | vec![ | 317 | vec![ |
| 275 | (0x14001d7a, modified.to_vec()), | 318 | (0x14001d7a, modified.to_vec()), |
| 276 | (0x1c001c22, string(&self.text)), | 319 | (0x1c001c22, string(&self.text)), |
| 277 | (0x24001e13, reference(5)), | 320 | (0x24001e13, reference(default)?), |
| 278 | (0x10001cfe, 0x409_u16.to_le_bytes().to_vec()), | 321 | (0x10001cfe, 0x409_u16.to_le_bytes().to_vec()), |
| 279 | ], | 322 | ], |
| 280 | ), | 323 | ), |
| 281 | (4, 0x120001, vec![(0x1c001d75, string(&self.author))]), | 324 | (author, 0x120001, vec![(0x1c001d75, string(&self.author))]), |
| 282 | (5, 0x12004d, default_text_style()), | 325 | (default, 0x12004d, default_text_style()), |
| 283 | ] { | 326 | ] { |
| 284 | new.insert( | 327 | new.insert( |
| 285 | ExGuid { guid: self.guid, n }, | 328 | id, |
| 286 | PropertyObject { | 329 | PropertyObject { |
| 287 | jcid, | 330 | jcid, |
| 288 | bytes: properties(&values)?, | 331 | bytes: properties(&values)?, |
| ... | @@ -291,10 +334,6 @@ impl Insertion { | ... | @@ -291,10 +334,6 @@ impl Insertion { |
| 291 | ); | 334 | ); |
| 292 | } | 335 | } |
| 293 | if !self.formats.is_empty() { | 336 | if !self.formats.is_empty() { |
| 294 | let default = ExGuid { | ||
| 295 | guid: self.guid, | ||
| 296 | n: 5, | ||
| 297 | }; | ||
| 298 | let mut segments = Vec::new(); | 337 | let mut segments = Vec::new(); |
| 299 | let mut end = 0; | 338 | let mut end = 0; |
| 300 | for span in &self.formats { | 339 | for span in &self.formats { |
| ... | @@ -329,7 +368,7 @@ impl Insertion { | ... | @@ -329,7 +368,7 @@ impl Insertion { |
| 329 | if !segments.iter().any(|(_, id)| *id == default) { | 368 | if !segments.iter().any(|(_, id)| *id == default) { |
| 330 | new.remove(&default); | 369 | new.remove(&default); |
| 331 | } | 370 | } |
| 332 | let target = new.get_mut(&self.text_object()).unwrap(); | 371 | let target = new.get_mut(&text).unwrap(); |
| 333 | let mut references = Vec::new(); | 372 | let mut references = Vec::new(); |
| 334 | let mut ends = Vec::new(); | 373 | let mut ends = Vec::new(); |
| 335 | for (end, id) in segments { | 374 | for (end, id) in segments { |
| ... | @@ -340,16 +379,17 @@ impl Insertion { | ... | @@ -340,16 +379,17 @@ impl Insertion { |
| 340 | target.set(&[(0x24001e13, &references), (0x1c001e12, &ends)])?; | 379 | target.set(&[(0x24001e13, &references), (0x1c001e12, &ends)])?; |
| 341 | } | 380 | } |
| 342 | if let Placement::Outline { x, y } = self.placement { | 381 | if let Placement::Outline { x, y } = self.placement { |
| 382 | let children = reference(paragraph)?; | ||
| 343 | new.insert( | 383 | new.insert( |
| 344 | self.object(), | 384 | object, |
| 345 | PropertyObject { | 385 | PropertyObject { |
| 346 | jcid: 0x6000c, | 386 | jcid: 0x6000c, |
| 347 | global_ids: table, | 387 | global_ids: table, |
| 348 | bytes: properties(&[ | 388 | bytes: properties(&[ |
| 349 | (0x14001d7a, modified.to_vec()), | 389 | (0x14001d7a, modified.to_vec()), |
| 350 | (0x24001c20, reference(3)), | 390 | (0x24001c20, children), |
| 351 | (0x0c001c03, vec![1]), | 391 | (0x0c001c03, vec![1]), |
| 352 | (0x1c001c12, vec![1, 0, 0, 0, 0, 0, 0, 0]), | 392 | (0x1c001c12, measurement_bytes(&NATIVE_INDENTS, 4)?), |
| 353 | (0x14001c14, (x / 36.0).to_le_bytes().to_vec()), | 393 | (0x14001c14, (x / 36.0).to_le_bytes().to_vec()), |
| 354 | (0x14001c15, (y / 36.0).to_le_bytes().to_vec()), | 394 | (0x14001c15, (y / 36.0).to_le_bytes().to_vec()), |
| 355 | (0x14001c1b, 13_f32.to_le_bytes().to_vec()), | 395 | (0x14001c1b, 13_f32.to_le_bytes().to_vec()), |
| ... | @@ -359,16 +399,11 @@ impl Insertion { | ... | @@ -359,16 +399,11 @@ impl Insertion { |
| 359 | ); | 399 | ); |
| 360 | } | 400 | } |
| 361 | let raw = &active.live.revision; | 401 | let raw = &active.live.revision; |
| 362 | if new.keys().any(|id| raw.objects.contains_key(id)) { | ||
| 363 | return Err(invalid( | ||
| 364 | "An insertion identity is already present; reconcile the existing edit", | ||
| 365 | )); | ||
| 366 | } | ||
| 367 | // Drop the overlay before moving the property bytes it borrows. | 402 | // Drop the overlay before moving the property bytes it borrows. |
| 368 | let title = { | 403 | let title = { |
| 369 | let mut overlay = BTreeMap::new(); | 404 | let mut overlay = BTreeMap::new(); |
| 370 | let mut parent = view.nodes[&self.parent].clone(); | 405 | let mut parent = view.nodes[&self.parent].clone(); |
| 371 | parent.children.insert(position, self.object()); | 406 | parent.children.insert(position, object); |
| 372 | overlay.insert(self.parent, parent); | 407 | overlay.insert(self.parent, parent); |
| 373 | for (id, object) in &new { | 408 | for (id, object) in &new { |
| 374 | overlay.insert( | 409 | overlay.insert( |
| ... | @@ -397,7 +432,7 @@ impl Insertion { | ... | @@ -397,7 +432,7 @@ impl Insertion { |
| 397 | None => Vec::new(), | 432 | None => Vec::new(), |
| 398 | _ => return Err(invalid("The parent has an invalid child list")), | 433 | _ => return Err(invalid("The parent has an invalid child list")), |
| 399 | }; | 434 | }; |
| 400 | let child = parent.reference(self.object())?; | 435 | let child = parent.reference(object)?; |
| 401 | if position > ids.len() / 4 { | 436 | if position > ids.len() / 4 { |
| 402 | return Err(invalid("The parent has an invalid child list")); | 437 | return Err(invalid("The parent has an invalid child list")); |
| 403 | } | 438 | } |
crates/onestore/src/lib.rs+6-3| ... | @@ -12,6 +12,7 @@ mod flush; | ... | @@ -12,6 +12,7 @@ mod flush; |
| 12 | mod formatting; | 12 | mod formatting; |
| 13 | mod insertion; | 13 | mod insertion; |
| 14 | mod objects; | 14 | mod objects; |
| 15 | pub mod op; | ||
| 15 | mod outline; | 16 | mod outline; |
| 16 | pub mod page; | 17 | pub mod page; |
| 17 | mod pages; | 18 | mod pages; |
| ... | @@ -20,6 +21,7 @@ mod properties; | ... | @@ -20,6 +21,7 @@ mod properties; |
| 20 | #[cfg(feature = "protected")] | 21 | #[cfg(feature = "protected")] |
| 21 | pub mod protected; | 22 | pub mod protected; |
| 22 | mod revisions; | 23 | mod revisions; |
| 24 | mod section; | ||
| 23 | mod snapshot; | 25 | mod snapshot; |
| 24 | mod store; | 26 | mod store; |
| 25 | mod toc; | 27 | mod toc; |
| ... | @@ -27,8 +29,8 @@ mod tree; | ... | @@ -27,8 +29,8 @@ mod tree; |
| 27 | mod write; | 29 | mod write; |
| 28 | 30 | ||
| 29 | pub use commit::{ | 31 | pub use commit::{ |
| 30 | CommitError, CommitIo, CommitState, PreparedEdit, commit_property_bytes, commit_text, | 32 | CommitError, CommitIo, CommitState, PreparedEdit, Stamp, Transaction, commit_property_bytes, |
| 31 | confirm_snapshot, place, | 33 | commit_text, confirm_snapshot, place, |
| 32 | }; | 34 | }; |
| 33 | #[cfg(any(unix, windows))] | 35 | #[cfg(any(unix, windows))] |
| 34 | pub use commit::{ | 36 | pub use commit::{ |
| ... | @@ -38,7 +40,7 @@ pub use commit::{ | ... | @@ -38,7 +40,7 @@ pub use commit::{ |
| 38 | pub use create::{create_section, create_table_of_contents}; | 40 | pub use create::{create_section, create_table_of_contents}; |
| 39 | pub use edit::replace_text; | 41 | pub use edit::replace_text; |
| 40 | pub use files::FileDataReference; | 42 | pub use files::FileDataReference; |
| 41 | pub use formatting::TextAttribute; | 43 | pub use formatting::{FONT_SIZES, TextAttribute}; |
| 42 | pub use insertion::Insertion; | 44 | pub use insertion::Insertion; |
| 43 | pub use objects::{Object, ObjectData, ObjectReferences, ResolvedRevision}; | 45 | pub use objects::{Object, ObjectData, ObjectReferences, ResolvedRevision}; |
| 44 | pub use outline::OutlineEdit; | 46 | pub use outline::OutlineEdit; |
| ... | @@ -46,6 +48,7 @@ pub use pages::{PageCreation, PageEdit, PagePosition}; | ... | @@ -46,6 +48,7 @@ pub use pages::{PageCreation, PageEdit, PagePosition}; |
| 46 | pub use paragraph::{ParagraphJoin, ParagraphSplit}; | 48 | pub use paragraph::{ParagraphJoin, ParagraphSplit}; |
| 47 | pub use properties::{IdStream, Property, PropertySets, Value}; | 49 | pub use properties::{IdStream, Property, PropertySets, Value}; |
| 48 | pub use revisions::{ExGuid, ObjectSpace, Revision, RevisionIndex}; | 50 | pub use revisions::{ExGuid, ObjectSpace, Revision, RevisionIndex}; |
| 51 | pub use section::{Arena, Section}; | ||
| 49 | pub use snapshot::{read_snapshot, read_storage_snapshot}; | 52 | pub use snapshot::{read_snapshot, read_storage_snapshot}; |
| 50 | pub use store::{Chunk, Error, FileType, Header, Node, NodeList, Reference, Store}; | 53 | pub use store::{Chunk, Error, FileType, Header, Node, NodeList, Reference, Store}; |
| 51 | pub use toc::TocEdit; | 54 | pub use toc::TocEdit; |
crates/onestore/src/objects.rs+2-2| ... | @@ -19,7 +19,7 @@ pub enum ObjectData<'a> { | ... | @@ -19,7 +19,7 @@ pub enum ObjectData<'a> { |
| 19 | }, | 19 | }, |
| 20 | } | 20 | } |
| 21 | 21 | ||
| 22 | #[derive(Debug)] | 22 | #[derive(Debug, Clone)] |
| 23 | pub struct Object<'a> { | 23 | pub struct Object<'a> { |
| 24 | pub jcid: u32, | 24 | pub jcid: u32, |
| 25 | pub reference_count: u32, | 25 | pub reference_count: u32, |
| ... | @@ -27,7 +27,7 @@ pub struct Object<'a> { | ... | @@ -27,7 +27,7 @@ pub struct Object<'a> { |
| 27 | pub global_ids: Arc<GlobalIds>, | 27 | pub global_ids: Arc<GlobalIds>, |
| 28 | } | 28 | } |
| 29 | 29 | ||
| 30 | #[derive(Debug)] | 30 | #[derive(Debug, Clone)] |
| 31 | pub struct ResolvedRevision<'a> { | 31 | pub struct ResolvedRevision<'a> { |
| 32 | pub roots: BTreeMap<u32, ExGuid>, | 32 | pub roots: BTreeMap<u32, ExGuid>, |
| 33 | pub objects: BTreeMap<ExGuid, Object<'a>>, | 33 | pub objects: BTreeMap<ExGuid, Object<'a>>, |
crates/onestore/src/op/apply.rs created+1188| ... | @@ -0,0 +1,1188 @@ | ||
| 1 | //! `Section::apply`: each op checked against the page it names, then written through the | ||
| 2 | //! typed writers onto the section's copy of that page. | ||
| 3 | |||
| 4 | use super::{ | ||
| 5 | Edit, Op, OpError, PageOp, SectionOp, TableEdit, TextProperty, | ||
| 6 | content::{self, AttachmentIds}, | ||
| 7 | levels, | ||
| 8 | lower::{ParagraphFields, format_edits, paragraph_fields}, | ||
| 9 | properties, | ||
| 10 | table::{self, Structure}, | ||
| 11 | }; | ||
| 12 | use crate::{ | ||
| 13 | Error, ExGuid, Insertion, OutlineEdit, ParagraphJoin, ParagraphSplit, Section, TextAttribute, | ||
| 14 | TreeEdit, | ||
| 15 | active::{ActivePage, Changes}, | ||
| 16 | document::Kind, | ||
| 17 | page::{ | ||
| 18 | Attachment, Image, Ink, Page, PageObject, PageParagraph, Paragraph, ParagraphContent, | ||
| 19 | TableCell, TableColumn, | ||
| 20 | }, | ||
| 21 | }; | ||
| 22 | use std::{ | ||
| 23 | collections::{BTreeMap, BTreeSet}, | ||
| 24 | ops::Range, | ||
| 25 | }; | ||
| 26 | |||
| 27 | fn invalid(message: &'static str) -> Error { | ||
| 28 | Error { offset: 0, message } | ||
| 29 | } | ||
| 30 | |||
| 31 | impl<'a> Section<'a> { | ||
| 32 | /// Applies an edit whole or not at all, `author` naming who made it. Its modification | ||
| 33 | /// times are `edit.at`. | ||
| 34 | pub fn apply(&mut self, author: &str, edit: &Edit) -> Result<(), OpError> { | ||
| 35 | if author.contains('\0') { | ||
| 36 | return Err(OpError::Unsupported("Choose an author name without NUL")); | ||
| 37 | } | ||
| 38 | let undo = edit.ops.len() > 1 || edit.ops.iter().any(stores_more_than_once); | ||
| 39 | crate::create::at(edit.at, || { | ||
| 40 | self.atomically(undo, |section| { | ||
| 41 | for op in &edit.ops { | ||
| 42 | match op { | ||
| 43 | Op::Page { space, op } => Writer { | ||
| 44 | section: &mut *section, | ||
| 45 | space: *space, | ||
| 46 | author, | ||
| 47 | } | ||
| 48 | .apply(op)?, | ||
| 49 | Op::Section(op) => section.apply_section(author, op)?, | ||
| 50 | } | ||
| 51 | } | ||
| 52 | Ok(()) | ||
| 53 | }) | ||
| 54 | }) | ||
| 55 | .map_err(|error| self.classify(error)) | ||
| 56 | } | ||
| 57 | |||
| 58 | /// Lowers `after` against the page `space` holds and applies the result as one edit; | ||
| 59 | /// returns the ops, which reach `after` where the writers can store it. | ||
| 60 | pub fn apply_page( | ||
| 61 | &mut self, | ||
| 62 | author: &str, | ||
| 63 | at: u64, | ||
| 64 | space: ExGuid, | ||
| 65 | after: &Page, | ||
| 66 | ) -> Result<Vec<PageOp>, OpError> { | ||
| 67 | let before = self.page(space).map_err(|error| self.classify(Failure::Rejected(error)))?; | ||
| 68 | let ops = super::lower_page(&before, after) | ||
| 69 | .map_err(|error| OpError::Unsupported(error.message))?; | ||
| 70 | let edit = Edit { | ||
| 71 | at, | ||
| 72 | ops: ops | ||
| 73 | .iter() | ||
| 74 | .map(|op| Op::Page { | ||
| 75 | space, | ||
| 76 | op: op.clone(), | ||
| 77 | }) | ||
| 78 | .collect(), | ||
| 79 | }; | ||
| 80 | self.apply(author, &edit)?; | ||
| 81 | Ok(ops) | ||
| 82 | } | ||
| 83 | |||
| 84 | fn classify(&self, failure: Failure) -> OpError { | ||
| 85 | match failure { | ||
| 86 | Failure::Refused(error) => error, | ||
| 87 | Failure::Rejected(error) if self.broken() => OpError::Failed(error), | ||
| 88 | Failure::Rejected(error) => OpError::Unsupported(error.message), | ||
| 89 | } | ||
| 90 | } | ||
| 91 | |||
| 92 | fn apply_section(&mut self, author: &str, op: &SectionOp) -> Result<(), Failure> { | ||
| 93 | match op { | ||
| 94 | SectionOp::Create(creation) => { | ||
| 95 | let (root, roots, objects) = creation.changes(self)?; | ||
| 96 | self.apply_changes(self.root(), &[], root)?; | ||
| 97 | self.create_space(creation.space(), roots, objects)?; | ||
| 98 | } | ||
| 99 | SectionOp::Import { creation, page } => { | ||
| 100 | self.apply_section(author, &SectionOp::Create(creation.clone()))?; | ||
| 101 | let space = creation.space(); | ||
| 102 | let before = self.page(space)?; | ||
| 103 | let mut after = page.clone(); | ||
| 104 | // The created page's title stays; the imported page brings the rest. | ||
| 105 | after.objects.retain(|object| !matches!(object, PageObject::Title(_))); | ||
| 106 | for object in before.objects.iter().rev() { | ||
| 107 | if let PageObject::Title(_) = object { | ||
| 108 | after.objects.insert(0, object.clone()); | ||
| 109 | } | ||
| 110 | } | ||
| 111 | after.title = before.title.clone(); | ||
| 112 | after.identity = before.identity; | ||
| 113 | after.created = before.created; | ||
| 114 | after.margin_origin = before.margin_origin; | ||
| 115 | for op in super::lower_page(&before, &after)? { | ||
| 116 | Writer { | ||
| 117 | section: self, | ||
| 118 | space, | ||
| 119 | author, | ||
| 120 | } | ||
| 121 | .apply(&op)?; | ||
| 122 | } | ||
| 123 | } | ||
| 124 | SectionOp::Pages(edits) => { | ||
| 125 | for (space, changes) in crate::pages::section_changes(self, edits, &[])? { | ||
| 126 | self.apply_changes(space, &[], changes)?; | ||
| 127 | } | ||
| 128 | } | ||
| 129 | SectionOp::Delete(pages) => { | ||
| 130 | for (space, changes) in crate::pages::section_changes(self, &[], pages)? { | ||
| 131 | self.apply_changes(space, &[], changes)?; | ||
| 132 | } | ||
| 133 | } | ||
| 134 | } | ||
| 135 | Ok(()) | ||
| 136 | } | ||
| 137 | } | ||
| 138 | |||
| 139 | /// Whether an op may store more than one revision of a page, so its failure part way needs | ||
| 140 | /// the page as it was. | ||
| 141 | fn stores_more_than_once(op: &Op) -> bool { | ||
| 142 | match op { | ||
| 143 | Op::Section(_) => true, | ||
| 144 | Op::Page { op, .. } => matches!( | ||
| 145 | op, | ||
| 146 | PageOp::Link { .. } | ||
| 147 | | PageOp::Insert { .. } | ||
| 148 | | PageOp::Move { .. } | ||
| 149 | | PageOp::Add { .. } | ||
| 150 | | PageOp::Table { | ||
| 151 | edit: TableEdit::Rows { .. } | TableEdit::Column { .. }, | ||
| 152 | .. | ||
| 153 | } | ||
| 154 | ), | ||
| 155 | } | ||
| 156 | } | ||
| 157 | |||
| 158 | /// Why an op was not applied: a precondition it names, or what a writer refused. | ||
| 159 | enum Failure { | ||
| 160 | Refused(OpError), | ||
| 161 | Rejected(Error), | ||
| 162 | } | ||
| 163 | |||
| 164 | impl From<Error> for Failure { | ||
| 165 | fn from(error: Error) -> Self { | ||
| 166 | Self::Rejected(error) | ||
| 167 | } | ||
| 168 | } | ||
| 169 | |||
| 170 | impl From<OpError> for Failure { | ||
| 171 | fn from(error: OpError) -> Self { | ||
| 172 | Self::Refused(error) | ||
| 173 | } | ||
| 174 | } | ||
| 175 | |||
| 176 | /// Writes ops onto one page of a section. | ||
| 177 | struct Writer<'s, 'a> { | ||
| 178 | section: &'s mut Section<'a>, | ||
| 179 | space: ExGuid, | ||
| 180 | author: &'s str, | ||
| 181 | } | ||
| 182 | |||
| 183 | impl<'a> Writer<'_, 'a> { | ||
| 184 | fn page(&mut self) -> Result<&ActivePage<'a>, Error> { | ||
| 185 | self.section.active(self.space) | ||
| 186 | } | ||
| 187 | |||
| 188 | fn write( | ||
| 189 | &mut self, | ||
| 190 | changes: impl FnOnce(&ActivePage<'a>) -> Result<Changes, Error>, | ||
| 191 | ) -> Result<(), Failure> { | ||
| 192 | self.write_with(&[], changes) | ||
| 193 | } | ||
| 194 | |||
| 195 | fn write_with( | ||
| 196 | &mut self, | ||
| 197 | payloads: &[([u8; 16], &[u8])], | ||
| 198 | changes: impl FnOnce(&ActivePage<'a>) -> Result<Changes, Error>, | ||
| 199 | ) -> Result<(), Failure> { | ||
| 200 | let changes = changes(self.page()?)?; | ||
| 201 | self.section.apply_changes(self.space, payloads, changes)?; | ||
| 202 | Ok(()) | ||
| 203 | } | ||
| 204 | |||
| 205 | /// Requires `id` reachable on the page. | ||
| 206 | fn target(&mut self, id: ExGuid) -> Result<(), Failure> { | ||
| 207 | let page = self.page()?; | ||
| 208 | if page.live.is_reachable(id) && page.view.nodes.contains_key(&id) { | ||
| 209 | Ok(()) | ||
| 210 | } else { | ||
| 211 | Err(OpError::TargetUnavailable(id).into()) | ||
| 212 | } | ||
| 213 | } | ||
| 214 | |||
| 215 | /// Requires `id` free for a new object: not reachable on the page. | ||
| 216 | fn free(&mut self, id: ExGuid) -> Result<(), Failure> { | ||
| 217 | if id.guid == [0; 16] || self.page()?.live.is_reachable(id) { | ||
| 218 | Err(OpError::DuplicateIdentity(id).into()) | ||
| 219 | } else { | ||
| 220 | Ok(()) | ||
| 221 | } | ||
| 222 | } | ||
| 223 | |||
| 224 | /// The only parent of `id` on the page. | ||
| 225 | fn parent(&mut self, id: ExGuid) -> Result<ExGuid, Failure> { | ||
| 226 | match self.page()?.parents.get(&id).map(Vec::as_slice) { | ||
| 227 | Some([parent]) => Ok(*parent), | ||
| 228 | _ => Err(OpError::StructureChanged("Page content must have one parent").into()), | ||
| 229 | } | ||
| 230 | } | ||
| 231 | |||
| 232 | /// The container `id` lies in, outline groups passed over. | ||
| 233 | fn container(&mut self, id: ExGuid) -> Result<ExGuid, Failure> { | ||
| 234 | let mut at = self.parent(id)?; | ||
| 235 | while matches!(self.page()?.view.nodes[&at].kind, Kind::OutlineGroup) { | ||
| 236 | at = self.parent(at)?; | ||
| 237 | } | ||
| 238 | Ok(at) | ||
| 239 | } | ||
| 240 | |||
| 241 | /// The text object a paragraph holds. | ||
| 242 | fn text_of(&mut self, paragraph: ExGuid) -> Result<ExGuid, Failure> { | ||
| 243 | let page = self.page()?; | ||
| 244 | let node = &page.view.nodes[&paragraph]; | ||
| 245 | match node.content.as_slice() { | ||
| 246 | [text] if matches!(page.view.nodes[text].kind, Kind::RichText { .. }) => Ok(*text), | ||
| 247 | _ => Err(OpError::Unsupported("Select a paragraph holding text").into()), | ||
| 248 | } | ||
| 249 | } | ||
| 250 | |||
| 251 | /// The absolute outline level of paragraph `id`; zero for an outline or cell. | ||
| 252 | fn level(&mut self, id: ExGuid) -> Result<u32, Failure> { | ||
| 253 | if !matches!(self.page()?.view.nodes[&id].kind, Kind::Paragraph { .. }) { | ||
| 254 | return Ok(0); | ||
| 255 | } | ||
| 256 | let container = self.container(id)?; | ||
| 257 | let depth = self.depth(container, id)?; | ||
| 258 | Ok(self.level(container)? + depth) | ||
| 259 | } | ||
| 260 | |||
| 261 | fn depth(&mut self, container: ExGuid, id: ExGuid) -> Result<u32, Failure> { | ||
| 262 | levels::stored_depths(&self.page()?.view, container) | ||
| 263 | .into_iter() | ||
| 264 | .find(|(child, _)| *child == id) | ||
| 265 | .map(|(_, depth)| depth) | ||
| 266 | .ok_or_else(|| OpError::StructureChanged("A paragraph left its container").into()) | ||
| 267 | } | ||
| 268 | |||
| 269 | /// Stores `container`'s children at the depths they have, `targets` overriding some. | ||
| 270 | fn regroup(&mut self, container: ExGuid, targets: &[(ExGuid, u32)]) -> Result<(), Failure> { | ||
| 271 | let page = self.page()?; | ||
| 272 | let current = levels::stored_depths(&page.view, container); | ||
| 273 | let is_cell = matches!(page.view.nodes[&container].kind, Kind::Cell { .. }); | ||
| 274 | let (children, depths): (Vec<ExGuid>, Vec<u32>) = current | ||
| 275 | .iter() | ||
| 276 | .map(|(child, depth)| { | ||
| 277 | let target = targets | ||
| 278 | .iter() | ||
| 279 | .find(|(id, _)| id == child) | ||
| 280 | .map_or(*depth, |(_, depth)| *depth); | ||
| 281 | (*child, target) | ||
| 282 | }) | ||
| 283 | .unzip(); | ||
| 284 | if current.iter().map(|(_, depth)| *depth).eq(depths.iter().copied()) { | ||
| 285 | return Ok(()); | ||
| 286 | } | ||
| 287 | let (level, runs) = levels::runs(&children, &depths, is_cell)?; | ||
| 288 | self.write(|page| levels::regroup_changes(page, container, level, &runs)) | ||
| 289 | } | ||
| 290 | |||
| 291 | fn apply(&mut self, op: &PageOp) -> Result<(), Failure> { | ||
| 292 | match op { | ||
| 293 | PageOp::Text { text, range, with } => { | ||
| 294 | self.target(*text)?; | ||
| 295 | if with.contains(['\0', '\n', '\u{fffc}']) { | ||
| 296 | return Err(OpError::Unsupported("Use ordinary paragraph text").into()); | ||
| 297 | } | ||
| 298 | self.write(|page| crate::edit::text_changes(page, *text, range.clone(), with)) | ||
| 299 | } | ||
| 300 | PageOp::Format { | ||
| 301 | text, | ||
| 302 | range, | ||
| 303 | set, | ||
| 304 | clear, | ||
| 305 | } => { | ||
| 306 | self.target(*text)?; | ||
| 307 | self.write(|page| { | ||
| 308 | crate::formatting::format_changes(page, *text, range.clone(), set, clear) | ||
| 309 | }) | ||
| 310 | } | ||
| 311 | PageOp::Link { | ||
| 312 | text, | ||
| 313 | range, | ||
| 314 | target, | ||
| 315 | } => { | ||
| 316 | self.target(*text)?; | ||
| 317 | let page = self.page()?; | ||
| 318 | let current = crate::page::text_of(&page.view, &page.parents, *text)?; | ||
| 319 | for op in link_ops(&current, *text, range.clone(), target.as_deref())? { | ||
| 320 | self.apply(&op)?; | ||
| 321 | } | ||
| 322 | Ok(()) | ||
| 323 | } | ||
| 324 | PageOp::Equation { text, math } => { | ||
| 325 | self.target(*text)?; | ||
| 326 | self.write(|page| content::equation_changes(page, *text, math)) | ||
| 327 | } | ||
| 328 | PageOp::Insert { | ||
| 329 | container, | ||
| 330 | before, | ||
| 331 | paragraphs, | ||
| 332 | } => { | ||
| 333 | self.target(*container)?; | ||
| 334 | self.insert(*container, *before, paragraphs) | ||
| 335 | } | ||
| 336 | PageOp::Split { | ||
| 337 | text, | ||
| 338 | at, | ||
| 339 | paragraph, | ||
| 340 | right, | ||
| 341 | lists, | ||
| 342 | } => { | ||
| 343 | self.target(*text)?; | ||
| 344 | for id in [*paragraph, *right].iter().chain(lists) { | ||
| 345 | self.free(*id)?; | ||
| 346 | } | ||
| 347 | let split = ParagraphSplit::new(*text, *at, self.author)?; | ||
| 348 | self.write(|page| split.changes_as(page, *paragraph, *right, lists)) | ||
| 349 | } | ||
| 350 | PageOp::Join { left, right } => { | ||
| 351 | self.target(*left)?; | ||
| 352 | self.target(*right)?; | ||
| 353 | let join = ParagraphJoin::new(*left, *right, self.author)?; | ||
| 354 | self.write(|page| join.changes(page)) | ||
| 355 | } | ||
| 356 | PageOp::Move { | ||
| 357 | object, | ||
| 358 | parent, | ||
| 359 | before, | ||
| 360 | } => { | ||
| 361 | self.target(*object)?; | ||
| 362 | self.move_to(*object, *parent, *before) | ||
| 363 | } | ||
| 364 | PageOp::Delete { object } => { | ||
| 365 | self.target(*object)?; | ||
| 366 | let page = self.page()?; | ||
| 367 | if matches!(page.view.nodes[object].kind, Kind::Paragraph { .. }) { | ||
| 368 | let container = self.container(*object)?; | ||
| 369 | if matches!(self.page()?.view.nodes[&container].kind, Kind::Cell { .. }) | ||
| 370 | && levels::flat_children(&self.page()?.view, container) == [*object] | ||
| 371 | { | ||
| 372 | return Err(OpError::Unsupported( | ||
| 373 | "A table cell keeps a paragraph; insert its replacement first", | ||
| 374 | ) | ||
| 375 | .into()); | ||
| 376 | } | ||
| 377 | } | ||
| 378 | let edit = TreeEdit::delete(*object, self.author)?; | ||
| 379 | self.write(|page| edit.changes(page)) | ||
| 380 | } | ||
| 381 | PageOp::Level { paragraph, level } => { | ||
| 382 | self.target(*paragraph)?; | ||
| 383 | let container = self.container(*paragraph)?; | ||
| 384 | let base = self.level(container)?; | ||
| 385 | let depth = level | ||
| 386 | .checked_sub(base) | ||
| 387 | .filter(|depth| *depth > 0) | ||
| 388 | .ok_or(OpError::Unsupported( | ||
| 389 | "A paragraph lies deeper than its parent", | ||
| 390 | ))?; | ||
| 391 | self.regroup(container, &[(*paragraph, depth)]) | ||
| 392 | } | ||
| 393 | PageOp::Outline { object, edit } => { | ||
| 394 | self.target(*object)?; | ||
| 395 | self.write(|page| edit.changes(page, *object)) | ||
| 396 | } | ||
| 397 | PageOp::Paragraph { | ||
| 398 | paragraph, | ||
| 399 | alignment, | ||
| 400 | rtl, | ||
| 401 | space_before, | ||
| 402 | space_after, | ||
| 403 | line_spacing, | ||
| 404 | language, | ||
| 405 | } => { | ||
| 406 | self.target(*paragraph)?; | ||
| 407 | let text = self.text_of(*paragraph)?; | ||
| 408 | let values = ParagraphFields { | ||
| 409 | alignment: *alignment, | ||
| 410 | rtl: *rtl, | ||
| 411 | space_before: *space_before, | ||
| 412 | space_after: *space_after, | ||
| 413 | line_spacing: *line_spacing, | ||
| 414 | language: *language, | ||
| 415 | } | ||
| 416 | .values()?; | ||
| 417 | if values.is_empty() { | ||
| 418 | return Ok(()); | ||
| 419 | } | ||
| 420 | self.write(|page| properties::paragraph_format_changes(page, text, &values)) | ||
| 421 | } | ||
| 422 | PageOp::Style { | ||
| 423 | paragraph, | ||
| 424 | style, | ||
| 425 | definition, | ||
| 426 | } => { | ||
| 427 | self.target(*paragraph)?; | ||
| 428 | let text = self.text_of(*paragraph)?; | ||
| 429 | self.write(|page| properties::style_changes(page, text, *style, Some(definition))) | ||
| 430 | } | ||
| 431 | PageOp::List { paragraph, lists } => { | ||
| 432 | self.target(*paragraph)?; | ||
| 433 | let page = self.page()?; | ||
| 434 | for (id, _) in lists { | ||
| 435 | if page | ||
| 436 | .parents | ||
| 437 | .get(id) | ||
| 438 | .is_some_and(|owners| owners.iter().any(|owner| owner != paragraph)) | ||
| 439 | { | ||
| 440 | return Err(OpError::Unsupported("A list node belongs to one paragraph").into()); | ||
| 441 | } | ||
| 442 | } | ||
| 443 | let nodes = lists | ||
| 444 | .iter() | ||
| 445 | .map(|(id, definition)| Ok((*id, properties::list_values(definition)?))) | ||
| 446 | .collect::<Result<Vec<_>, Error>>()?; | ||
| 447 | self.write(|page| properties::list_changes(page, *paragraph, &nodes)) | ||
| 448 | } | ||
| 449 | PageOp::Tags { | ||
| 450 | target, | ||
| 451 | tags, | ||
| 452 | definitions, | ||
| 453 | } => { | ||
| 454 | self.target(*target)?; | ||
| 455 | let mut entries = Vec::new(); | ||
| 456 | for tag in tags { | ||
| 457 | let definition = tag | ||
| 458 | .definition | ||
| 459 | .ok_or(OpError::Unsupported("A note tag names its definition"))?; | ||
| 460 | let model = definitions | ||
| 461 | .iter() | ||
| 462 | .find(|(id, _)| *id == definition) | ||
| 463 | .map(|(_, definition)| definition); | ||
| 464 | entries.push((definition, tag, model)); | ||
| 465 | } | ||
| 466 | self.write(|page| properties::tag_changes(page, *target, &entries)) | ||
| 467 | } | ||
| 468 | PageOp::Add { object, before } => self.add(object, *before), | ||
| 469 | PageOp::Picture { | ||
| 470 | picture, | ||
| 471 | layout, | ||
| 472 | alt, | ||
| 473 | } => { | ||
| 474 | self.target(*picture)?; | ||
| 475 | let page = self.page()?; | ||
| 476 | let node = &page.view.nodes[picture]; | ||
| 477 | if !matches!(node.kind, Kind::Image { .. }) { | ||
| 478 | return Err(OpError::Unsupported("Select a picture").into()); | ||
| 479 | } | ||
| 480 | let stored = Image::read(&page.view, *picture, node)?; | ||
| 481 | self.write(|page| { | ||
| 482 | content::picture_edit_changes( | ||
| 483 | page, | ||
| 484 | *picture, | ||
| 485 | (&stored.layout, &stored.alt), | ||
| 486 | layout, | ||
| 487 | alt, | ||
| 488 | ) | ||
| 489 | }) | ||
| 490 | } | ||
| 491 | PageOp::Attachment { | ||
| 492 | attachment, | ||
| 493 | filename, | ||
| 494 | source_path, | ||
| 495 | size, | ||
| 496 | } => { | ||
| 497 | self.target(*attachment)?; | ||
| 498 | let page = self.page()?; | ||
| 499 | let node = &page.view.nodes[attachment]; | ||
| 500 | if !matches!(node.kind, Kind::Attachment { .. }) { | ||
| 501 | return Err(OpError::Unsupported("Select an attachment").into()); | ||
| 502 | } | ||
| 503 | let stored = Attachment::read(&page.view, *attachment, node)?; | ||
| 504 | let edited = Attachment { | ||
| 505 | filename: filename.clone(), | ||
| 506 | source_path: source_path.clone(), | ||
| 507 | size: *size, | ||
| 508 | ..stored.clone() | ||
| 509 | }; | ||
| 510 | self.write(|page| content::attachment_edit_changes(page, &stored, &edited)) | ||
| 511 | } | ||
| 512 | PageOp::Strokes { ink, add, remove } => { | ||
| 513 | self.target(*ink)?; | ||
| 514 | let page = self.page()?; | ||
| 515 | let node = &page.view.nodes[ink]; | ||
| 516 | if !matches!(node.kind, Kind::Ink { .. }) { | ||
| 517 | return Err(OpError::Unsupported("Select ink").into()); | ||
| 518 | } | ||
| 519 | let stored = Ink::read(&page.view, *ink, node)?; | ||
| 520 | if !stored.groups.is_empty() { | ||
| 521 | return Err(OpError::Unsupported("Grouped ink keeps its strokes").into()); | ||
| 522 | } | ||
| 523 | for id in remove { | ||
| 524 | if !stored.strokes.iter().any(|stroke| stroke.id == *id) { | ||
| 525 | return Err(OpError::TargetUnavailable(*id).into()); | ||
| 526 | } | ||
| 527 | } | ||
| 528 | for stroke in add { | ||
| 529 | self.free(stroke.id)?; | ||
| 530 | } | ||
| 531 | let kept: Vec<ExGuid> = stored | ||
| 532 | .strokes | ||
| 533 | .iter() | ||
| 534 | .map(|stroke| stroke.id) | ||
| 535 | .filter(|id| !remove.contains(id)) | ||
| 536 | .collect(); | ||
| 537 | let added: Vec<_> = add.iter().map(|stroke| (stroke.id, stroke)).collect(); | ||
| 538 | self.write(|page| content::strokes_changes(page, *ink, &kept, &added)) | ||
| 539 | } | ||
| 540 | PageOp::Table { table, edit } => { | ||
| 541 | self.target(*table)?; | ||
| 542 | self.table(*table, edit) | ||
| 543 | } | ||
| 544 | } | ||
| 545 | } | ||
| 546 | |||
| 547 | /// Moves `object` before `before` in `parent`, or among the page's children. | ||
| 548 | fn move_to( | ||
| 549 | &mut self, | ||
| 550 | object: ExGuid, | ||
| 551 | parent: Option<ExGuid>, | ||
| 552 | before: Option<ExGuid>, | ||
| 553 | ) -> Result<(), Failure> { | ||
| 554 | let Some(parent) = parent else { | ||
| 555 | let [page] = self.page()?.pages[..] else { | ||
| 556 | return Err(OpError::Unsupported("Choose a page space holding one page").into()); | ||
| 557 | }; | ||
| 558 | if let Some(before) = before | ||
| 559 | && self.parent(before)? != page | ||
| 560 | { | ||
| 561 | return Err(OpError::StructureChanged("The anchor is not on the page").into()); | ||
| 562 | } | ||
| 563 | let edit = TreeEdit::move_to(object, page, before, self.author)?; | ||
| 564 | return self.write(|page| edit.changes(page)); | ||
| 565 | }; | ||
| 566 | self.target(parent)?; | ||
| 567 | let mut at = parent; | ||
| 568 | loop { | ||
| 569 | if at == object { | ||
| 570 | return Err(OpError::StructureChanged("A subtree cannot move inside itself").into()); | ||
| 571 | } | ||
| 572 | match self.page()?.parents.get(&at).map(Vec::as_slice) { | ||
| 573 | Some([up]) => at = *up, | ||
| 574 | _ => break, | ||
| 575 | } | ||
| 576 | } | ||
| 577 | let holder = match before { | ||
| 578 | Some(before) => { | ||
| 579 | self.target(before)?; | ||
| 580 | if self.container(before)? != parent { | ||
| 581 | return Err( | ||
| 582 | OpError::StructureChanged("The anchor is no child of the container").into(), | ||
| 583 | ); | ||
| 584 | } | ||
| 585 | self.parent(before)? | ||
| 586 | } | ||
| 587 | None => parent, | ||
| 588 | }; | ||
| 589 | let level = self.level(object)?; | ||
| 590 | let edit = TreeEdit::move_to(object, holder, before, self.author)?; | ||
| 591 | self.write(|page| edit.changes(page))?; | ||
| 592 | if matches!(self.page()?.view.nodes[&parent].kind, Kind::Cell { .. }) { | ||
| 593 | return Ok(()); | ||
| 594 | } | ||
| 595 | let base = self.level(parent)?; | ||
| 596 | let depth = if level > base { level - base } else { 1 }; | ||
| 597 | self.regroup(parent, &[(object, depth)]) | ||
| 598 | } | ||
| 599 | |||
| 600 | /// Inserts paragraphs into `container` before `before` as `PageOp::Insert` does. | ||
| 601 | fn insert( | ||
| 602 | &mut self, | ||
| 603 | container: ExGuid, | ||
| 604 | before: Option<ExGuid>, | ||
| 605 | paragraphs: &[PageParagraph], | ||
| 606 | ) -> Result<(), Failure> { | ||
| 607 | if paragraphs.is_empty() { | ||
| 608 | return Err(OpError::Unsupported("Insert at least one paragraph").into()); | ||
| 609 | } | ||
| 610 | let holder = match before { | ||
| 611 | Some(before) => { | ||
| 612 | self.target(before)?; | ||
| 613 | if self.container(before)? != container { | ||
| 614 | return Err( | ||
| 615 | OpError::StructureChanged("The anchor is no child of the container").into(), | ||
| 616 | ); | ||
| 617 | } | ||
| 618 | self.parent(before)? | ||
| 619 | } | ||
| 620 | None => container, | ||
| 621 | }; | ||
| 622 | let mut seen = BTreeSet::new(); | ||
| 623 | for paragraph in paragraphs { | ||
| 624 | bare(paragraph)?; | ||
| 625 | for id in super::model::identities(std::slice::from_ref(paragraph)) { | ||
| 626 | if !seen.insert(id) { | ||
| 627 | return Err(OpError::DuplicateIdentity(id).into()); | ||
| 628 | } | ||
| 629 | self.free(id)?; | ||
| 630 | } | ||
| 631 | if let Some(parent) = paragraph.parent | ||
| 632 | && parent != container | ||
| 633 | && !paragraphs.iter().any(|p| p.id == parent) | ||
| 634 | { | ||
| 635 | return Err(OpError::StructureChanged( | ||
| 636 | "An inserted paragraph's parent precedes it among them", | ||
| 637 | ) | ||
| 638 | .into()); | ||
| 639 | } | ||
| 640 | } | ||
| 641 | let base = self.level(container)?; | ||
| 642 | let mut targets: BTreeMap<ExGuid, Vec<(ExGuid, u32)>> = BTreeMap::new(); | ||
| 643 | for paragraph in paragraphs { | ||
| 644 | let (parent, anchor, holder) = match paragraph.parent.filter(|p| *p != container) { | ||
| 645 | Some(parent) => (parent, None, parent), | ||
| 646 | None => (container, before, holder), | ||
| 647 | }; | ||
| 648 | let parent_level = if parent == container { | ||
| 649 | base | ||
| 650 | } else { | ||
| 651 | paragraphs.iter().find(|p| p.id == parent).unwrap().level | ||
| 652 | }; | ||
| 653 | let depth = paragraph | ||
| 654 | .level | ||
| 655 | .checked_sub(parent_level) | ||
| 656 | .filter(|depth| *depth > 0) | ||
| 657 | .ok_or(OpError::Unsupported("A paragraph lies deeper than its parent"))?; | ||
| 658 | targets.entry(parent).or_default().push((paragraph.id, depth)); | ||
| 659 | self.paragraph(holder, anchor, paragraph, None)?; | ||
| 660 | } | ||
| 661 | for (parent, targets) in targets { | ||
| 662 | if !matches!(self.page()?.view.nodes[&parent].kind, Kind::Cell { .. }) { | ||
| 663 | self.regroup(parent, &targets)?; | ||
| 664 | } | ||
| 665 | } | ||
| 666 | Ok(()) | ||
| 667 | } | ||
| 668 | |||
| 669 | /// Creates one paragraph with its content in `holder` before `anchor`; with `outline`, | ||
| 670 | /// as the first paragraph of that new outline at its position. | ||
| 671 | fn paragraph( | ||
| 672 | &mut self, | ||
| 673 | holder: ExGuid, | ||
| 674 | anchor: Option<ExGuid>, | ||
| 675 | paragraph: &PageParagraph, | ||
| 676 | outline: Option<(ExGuid, f32, f32)>, | ||
| 677 | ) -> Result<(), Failure> { | ||
| 678 | let (text, text_id) = match &paragraph.content { | ||
| 679 | ParagraphContent::Text(text) => (text.text.text(), text.id), | ||
| 680 | _ => ( | ||
| 681 | "", | ||
| 682 | ExGuid { | ||
| 683 | guid: crate::write::fresh_guid()?, | ||
| 684 | n: 1, | ||
| 685 | }, | ||
| 686 | ), | ||
| 687 | }; | ||
| 688 | let equation = paragraph | ||
| 689 | .text() | ||
| 690 | .is_some_and(|text| crate::page::Math::is_equation(&text.text)); | ||
| 691 | let insertion = match outline { | ||
| 692 | Some((_, x, y)) => { | ||
| 693 | let page = self.page()?.pages.first().copied().unwrap_or_default(); | ||
| 694 | Insertion::outline(page, x, y, text, self.author)? | ||
| 695 | } | ||
| 696 | None => Insertion::paragraph(holder, anchor, text, self.author)?, | ||
| 697 | }; | ||
| 698 | let object = outline.map_or(paragraph.id, |(id, _, _)| id); | ||
| 699 | self.write(|page| insertion.changes_as(page, object, paragraph.id, text_id))?; | ||
| 700 | match &paragraph.content { | ||
| 701 | ParagraphContent::Text(target) => { | ||
| 702 | if equation { | ||
| 703 | return self.write(|page| content::equation_changes(page, text_id, &target.text)); | ||
| 704 | } | ||
| 705 | let page = self.page()?; | ||
| 706 | let stored = crate::page::text_of(&page.view, &page.parents, text_id)?; | ||
| 707 | let values = paragraph_fields(&stored, &target.text)?.values()?; | ||
| 708 | if !values.is_empty() { | ||
| 709 | self.write(|page| properties::paragraph_format_changes(page, text_id, &values))?; | ||
| 710 | } | ||
| 711 | let page = self.page()?; | ||
| 712 | let stored = crate::page::text_of(&page.view, &page.parents, text_id)?; | ||
| 713 | for (range, set, clear) in format_edits(&stored, &target.text, true)? { | ||
| 714 | self.write(|page| { | ||
| 715 | crate::formatting::format_changes(page, text_id, range, &set, &clear) | ||
| 716 | })?; | ||
| 717 | } | ||
| 718 | Ok(()) | ||
| 719 | } | ||
| 720 | ParagraphContent::Table(table) => { | ||
| 721 | super::table::validate_table(table)?; | ||
| 722 | let structure = Structure { | ||
| 723 | rows: table | ||
| 724 | .rows | ||
| 725 | .iter() | ||
| 726 | .map(|row| (row.id, row.cells.iter().map(|cell| cell.id).collect())) | ||
| 727 | .collect(), | ||
| 728 | new_rows: table.rows.iter().map(|row| row.id).collect(), | ||
| 729 | new_cells: table | ||
| 730 | .rows | ||
| 731 | .iter() | ||
| 732 | .flat_map(|row| &row.cells) | ||
| 733 | .map(|cell| (cell.id, (table::cell_indents(&cell.indents, None), cell.shading))) | ||
| 734 | .collect(), | ||
| 735 | columns: &table.columns, | ||
| 736 | borders: table.borders, | ||
| 737 | }; | ||
| 738 | self.write(|page| table::table_changes(page, table.id, Some(paragraph.id), &structure))?; | ||
| 739 | for cell in table.rows.iter().flat_map(|row| &row.cells) { | ||
| 740 | self.cell(cell)?; | ||
| 741 | } | ||
| 742 | Ok(()) | ||
| 743 | } | ||
| 744 | ParagraphContent::Image(image) => { | ||
| 745 | if image.layout.x.is_some() || image.layout.y.is_some() { | ||
| 746 | return Err(OpError::Unsupported("A paragraph picture has no position of its own").into()); | ||
| 747 | } | ||
| 748 | self.picture(image, Some(paragraph.id)) | ||
| 749 | } | ||
| 750 | ParagraphContent::Attachment(attachment) => { | ||
| 751 | let Some(bytes) = &attachment.bytes else { | ||
| 752 | return Err(OpError::Unsupported("A new attachment needs its payload").into()); | ||
| 753 | }; | ||
| 754 | let ids = AttachmentIds { | ||
| 755 | object: attachment.id, | ||
| 756 | file: fresh()?, | ||
| 757 | payload: crate::write::fresh_guid()?, | ||
| 758 | preview: match &attachment.preview { | ||
| 759 | Some(_) => Some((crate::write::fresh_guid()?, fresh()?)), | ||
| 760 | None => None, | ||
| 761 | }, | ||
| 762 | }; | ||
| 763 | let mut payloads: Vec<([u8; 16], &[u8])> = vec![(ids.payload, bytes)]; | ||
| 764 | if let (Some((payload, _)), Some(icon)) = (ids.preview, &attachment.preview) { | ||
| 765 | payloads.push((payload, icon)); | ||
| 766 | } | ||
| 767 | self.write_with(&payloads, |page| { | ||
| 768 | content::attachment_changes(page, attachment, &ids, paragraph.id) | ||
| 769 | }) | ||
| 770 | } | ||
| 771 | ParagraphContent::Ink(ink) => self.ink(ink, Some(paragraph.id)), | ||
| 772 | ParagraphContent::Unsupported(_) => { | ||
| 773 | Err(OpError::Unsupported("Unsupported content cannot be inserted").into()) | ||
| 774 | } | ||
| 775 | } | ||
| 776 | } | ||
| 777 | |||
| 778 | /// Fills a new cell with its paragraphs. | ||
| 779 | fn cell(&mut self, cell: &TableCell) -> Result<(), Failure> { | ||
| 780 | if cell.paragraphs.is_empty() { | ||
| 781 | return Err(OpError::Unsupported("A new table cell needs a paragraph").into()); | ||
| 782 | } | ||
| 783 | self.insert(cell.id, None, &cell.paragraphs) | ||
| 784 | } | ||
| 785 | |||
| 786 | fn picture(&mut self, image: &Image, holder: Option<ExGuid>) -> Result<(), Failure> { | ||
| 787 | let Some(bytes) = &image.bytes else { | ||
| 788 | return Err(OpError::Unsupported("A new picture needs its payload").into()); | ||
| 789 | }; | ||
| 790 | let (file, payload) = (fresh()?, crate::write::fresh_guid()?); | ||
| 791 | let bytes: &[u8] = bytes; | ||
| 792 | self.write_with(&[(payload, bytes)], |page| { | ||
| 793 | content::picture_changes(page, image, image.id, file, payload, holder) | ||
| 794 | }) | ||
| 795 | } | ||
| 796 | |||
| 797 | fn ink(&mut self, ink: &Ink, holder: Option<ExGuid>) -> Result<(), Failure> { | ||
| 798 | for stroke in &ink.strokes { | ||
| 799 | self.free(stroke.id)?; | ||
| 800 | } | ||
| 801 | let data = fresh()?; | ||
| 802 | let strokes: Vec<_> = ink.strokes.iter().map(|stroke| (stroke.id, stroke)).collect(); | ||
| 803 | self.write(|page| content::ink_changes(page, ink, ink.id, data, &strokes, holder)) | ||
| 804 | } | ||
| 805 | |||
| 806 | /// Adds a page object on top, then moves it before `before`. | ||
| 807 | fn add(&mut self, object: &PageObject, before: Option<ExGuid>) -> Result<(), Failure> { | ||
| 808 | self.free(object.id())?; | ||
| 809 | match object { | ||
| 810 | PageObject::Outline(outline) => { | ||
| 811 | let (Some(x), Some(y)) = (outline.layout.x, outline.layout.y) else { | ||
| 812 | return Err(OpError::Unsupported("A new outline needs a position").into()); | ||
| 813 | }; | ||
| 814 | let [first, rest @ ..] = outline.paragraphs.as_slice() else { | ||
| 815 | return Err(OpError::Unsupported("A new outline needs a paragraph").into()); | ||
| 816 | }; | ||
| 817 | for paragraph in &outline.paragraphs { | ||
| 818 | bare(paragraph)?; | ||
| 819 | for id in super::model::identities(std::slice::from_ref(paragraph)) { | ||
| 820 | self.free(id)?; | ||
| 821 | } | ||
| 822 | } | ||
| 823 | if first.parent.is_some() || first.level != 1 { | ||
| 824 | return Err(OpError::Unsupported("An outline starts at its first level").into()); | ||
| 825 | } | ||
| 826 | self.paragraph(outline.id, None, first, Some((outline.id, x, y)))?; | ||
| 827 | // Later paragraphs follow the first, their levels as the outline gives them. | ||
| 828 | if !rest.is_empty() { | ||
| 829 | let mut targets = vec![(first.id, 1)]; | ||
| 830 | for paragraph in rest { | ||
| 831 | let parent = paragraph.parent.unwrap_or(outline.id); | ||
| 832 | let parent_level = rest | ||
| 833 | .iter() | ||
| 834 | .chain([first]) | ||
| 835 | .find(|p| p.id == parent) | ||
| 836 | .map_or(0, |p| p.level); | ||
| 837 | let depth = paragraph | ||
| 838 | .level | ||
| 839 | .checked_sub(parent_level) | ||
| 840 | .filter(|depth| *depth > 0) | ||
| 841 | .ok_or(OpError::Unsupported("A paragraph lies deeper than its parent"))?; | ||
| 842 | self.paragraph(parent, None, paragraph, None)?; | ||
| 843 | if parent == outline.id { | ||
| 844 | targets.push((paragraph.id, depth)); | ||
| 845 | } else { | ||
| 846 | self.regroup(parent, &[(paragraph.id, depth)])?; | ||
| 847 | } | ||
| 848 | } | ||
| 849 | self.regroup(outline.id, &targets)?; | ||
| 850 | } | ||
| 851 | // A new outline is 468 points wide unless it says otherwise. | ||
| 852 | if let Some(points) = outline.layout.max_width | ||
| 853 | && (points, outline.layout.width_set_by_user) != (468.0, None) | ||
| 854 | { | ||
| 855 | let edit = OutlineEdit::Width { | ||
| 856 | points, | ||
| 857 | user_set: outline.layout.width_set_by_user == Some(true), | ||
| 858 | }; | ||
| 859 | self.write(|page| edit.changes(page, outline.id))?; | ||
| 860 | } | ||
| 861 | if !outline.indents.is_empty() && outline.indents != content::NATIVE_INDENTS { | ||
| 862 | let indents = content::measurement_bytes(&outline.indents, 4)?; | ||
| 863 | let id = outline.id; | ||
| 864 | self.write(|page| { | ||
| 865 | let mut node = | ||
| 866 | crate::write::PropertyObject::from_object(&page.live.revision.objects[&id])?; | ||
| 867 | node.set(&[(0x1c001c12, &indents)])?; | ||
| 868 | Ok(BTreeMap::from([(id, node)])) | ||
| 869 | })?; | ||
| 870 | } | ||
| 871 | } | ||
| 872 | PageObject::Image(image) => { | ||
| 873 | if image.layout.x.is_none() || image.layout.y.is_none() { | ||
| 874 | return Err(OpError::Unsupported("A new page-level picture needs a position").into()); | ||
| 875 | } | ||
| 876 | self.picture(image, None)?; | ||
| 877 | } | ||
| 878 | PageObject::Ink(ink) => self.ink(ink, None)?, | ||
| 879 | PageObject::Title(_) | PageObject::Unsupported(_) => { | ||
| 880 | return Err(OpError::Unsupported("Titles and unsupported objects cannot be added").into()); | ||
| 881 | } | ||
| 882 | } | ||
| 883 | if before.is_some() { | ||
| 884 | self.move_to(object.id(), None, before)?; | ||
| 885 | } | ||
| 886 | Ok(()) | ||
| 887 | } | ||
| 888 | |||
| 889 | fn table(&mut self, id: ExGuid, edit: &TableEdit) -> Result<(), Failure> { | ||
| 890 | let page = self.page()?; | ||
| 891 | let node = &page.view.nodes[&id]; | ||
| 892 | let Kind::Table { | ||
| 893 | widths, | ||
| 894 | locked, | ||
| 895 | borders, | ||
| 896 | .. | ||
| 897 | } = &node.kind | ||
| 898 | else { | ||
| 899 | return Err(OpError::Unsupported("Select a table").into()); | ||
| 900 | }; | ||
| 901 | let mut columns: Vec<TableColumn> = widths | ||
| 902 | .iter() | ||
| 903 | .enumerate() | ||
| 904 | .map(|(i, width)| TableColumn { | ||
| 905 | width: *width, | ||
| 906 | locked: locked.get(i).copied().unwrap_or(false), | ||
| 907 | }) | ||
| 908 | .collect(); | ||
| 909 | let mut rows: Vec<(ExGuid, Vec<ExGuid>)> = node | ||
| 910 | .children | ||
| 911 | .iter() | ||
| 912 | .map(|row| (*row, page.view.nodes[row].children.clone())) | ||
| 913 | .collect(); | ||
| 914 | let mut borders = *borders; | ||
| 915 | let template = rows | ||
| 916 | .first() | ||
| 917 | .and_then(|(_, cells)| cells.first()) | ||
| 918 | .and_then(|cell| match &page.view.nodes[cell].kind { | ||
| 919 | Kind::Cell { indents, .. } => Some(indents.clone()), | ||
| 920 | _ => None, | ||
| 921 | }); | ||
| 922 | let mut new_rows = BTreeSet::new(); | ||
| 923 | let mut new_cells: BTreeMap<ExGuid, (Vec<f32>, Option<u32>)> = BTreeMap::new(); | ||
| 924 | let mut filled: Vec<&TableCell> = Vec::new(); | ||
| 925 | let new_cell = |cell: &TableCell| { | ||
| 926 | ( | ||
| 927 | table::cell_indents(&cell.indents, template.as_deref()).to_vec(), | ||
| 928 | cell.shading, | ||
| 929 | ) | ||
| 930 | }; | ||
| 931 | match edit { | ||
| 932 | TableEdit::Rows { before, rows: added } => { | ||
| 933 | let at = match before { | ||
| 934 | Some(before) => rows | ||
| 935 | .iter() | ||
| 936 | .position(|(row, _)| row == before) | ||
| 937 | .ok_or(OpError::StructureChanged("The anchor row is not in the table"))?, | ||
| 938 | None => rows.len(), | ||
| 939 | }; | ||
| 940 | for row in added { | ||
| 941 | if row.cells.len() != columns.len() { | ||
| 942 | return Err(OpError::Unsupported("Every table row has one cell per column").into()); | ||
| 943 | } | ||
| 944 | new_rows.insert(row.id); | ||
| 945 | for cell in &row.cells { | ||
| 946 | new_cells.insert(cell.id, new_cell(cell)); | ||
| 947 | filled.push(cell); | ||
| 948 | } | ||
| 949 | } | ||
| 950 | rows.splice( | ||
| 951 | at..at, | ||
| 952 | added | ||
| 953 | .iter() | ||
| 954 | .map(|row| (row.id, row.cells.iter().map(|cell| cell.id).collect())), | ||
| 955 | ); | ||
| 956 | } | ||
| 957 | TableEdit::Column { at, width, cells } => { | ||
| 958 | let at = *at as usize; | ||
| 959 | if at > columns.len() || cells.len() != rows.len() { | ||
| 960 | return Err(OpError::Unsupported("A new column has one cell per row").into()); | ||
| 961 | } | ||
| 962 | for ((_, row), cell) in rows.iter_mut().zip(cells) { | ||
| 963 | row.insert(at, cell.id); | ||
| 964 | new_cells.insert(cell.id, new_cell(cell)); | ||
| 965 | filled.push(cell); | ||
| 966 | } | ||
| 967 | columns.insert( | ||
| 968 | at, | ||
| 969 | TableColumn { | ||
| 970 | width: *width, | ||
| 971 | locked: false, | ||
| 972 | }, | ||
| 973 | ); | ||
| 974 | } | ||
| 975 | TableEdit::DeleteRow(row) => { | ||
| 976 | let before = rows.len(); | ||
| 977 | rows.retain(|(id, _)| id != row); | ||
| 978 | if rows.len() == before { | ||
| 979 | return Err(OpError::TargetUnavailable(*row).into()); | ||
| 980 | } | ||
| 981 | if rows.is_empty() { | ||
| 982 | return Err(OpError::Unsupported("A table keeps a row; delete the table instead").into()); | ||
| 983 | } | ||
| 984 | } | ||
| 985 | TableEdit::DeleteColumn(at) => { | ||
| 986 | let at = *at as usize; | ||
| 987 | if at >= columns.len() || columns.len() == 1 { | ||
| 988 | return Err(OpError::Unsupported("Choose a column of a table with several").into()); | ||
| 989 | } | ||
| 990 | columns.remove(at); | ||
| 991 | for (_, cells) in &mut rows { | ||
| 992 | cells.remove(at); | ||
| 993 | } | ||
| 994 | } | ||
| 995 | TableEdit::Columns(widths) => { | ||
| 996 | if widths.len() != columns.len() { | ||
| 997 | return Err(OpError::Unsupported("Give every column its width").into()); | ||
| 998 | } | ||
| 999 | columns = widths.clone(); | ||
| 1000 | } | ||
| 1001 | TableEdit::Borders(value) => borders = Some(*value), | ||
| 1002 | TableEdit::Cell { | ||
| 1003 | cell, | ||
| 1004 | shading, | ||
| 1005 | indents, | ||
| 1006 | } => { | ||
| 1007 | if !rows.iter().any(|(_, cells)| cells.contains(cell)) { | ||
| 1008 | return Err(OpError::TargetUnavailable(*cell).into()); | ||
| 1009 | } | ||
| 1010 | let Kind::Cell { | ||
| 1011 | shading: stored_shading, | ||
| 1012 | indents: stored_indents, | ||
| 1013 | } = &page.view.nodes[cell].kind | ||
| 1014 | else { | ||
| 1015 | return Err(OpError::Unsupported("Select a table cell").into()); | ||
| 1016 | }; | ||
| 1017 | let stored = (*stored_shading, stored_indents.clone()); | ||
| 1018 | return self.write(|page| { | ||
| 1019 | table::cell_changes(page, *cell, (stored.0, &stored.1), *shading, indents) | ||
| 1020 | }); | ||
| 1021 | } | ||
| 1022 | } | ||
| 1023 | for cell in &filled { | ||
| 1024 | for id in std::iter::once(cell.id).chain(super::model::identities(&cell.paragraphs)) { | ||
| 1025 | self.free(id)?; | ||
| 1026 | } | ||
| 1027 | } | ||
| 1028 | for row in &new_rows { | ||
| 1029 | self.free(*row)?; | ||
| 1030 | } | ||
| 1031 | if columns.iter().any(|c| !c.width.is_finite() || c.width < 36.0) { | ||
| 1032 | return Err(OpError::Unsupported("Table columns are at least 36 points wide").into()); | ||
| 1033 | } | ||
| 1034 | let structure = Structure { | ||
| 1035 | rows, | ||
| 1036 | new_rows, | ||
| 1037 | new_cells: new_cells | ||
| 1038 | .iter() | ||
| 1039 | .map(|(id, (indents, shading))| (*id, (indents.as_slice(), *shading))) | ||
| 1040 | .collect(), | ||
| 1041 | columns: &columns, | ||
| 1042 | borders, | ||
| 1043 | }; | ||
| 1044 | self.write(|page| table::table_changes(page, id, None, &structure))?; | ||
| 1045 | for cell in filled { | ||
| 1046 | self.cell(cell)?; | ||
| 1047 | } | ||
| 1048 | Ok(()) | ||
| 1049 | } | ||
| 1050 | } | ||
| 1051 | |||
| 1052 | fn fresh() -> Result<ExGuid, Error> { | ||
| 1053 | Ok(ExGuid { | ||
| 1054 | guid: crate::write::fresh_guid()?, | ||
| 1055 | n: 1, | ||
| 1056 | }) | ||
| 1057 | } | ||
| 1058 | |||
| 1059 | /// Requires what `Insert` leaves to other ops unset. | ||
| 1060 | fn bare(paragraph: &PageParagraph) -> Result<(), OpError> { | ||
| 1061 | let text_tags = paragraph.text().is_some_and(|text| !text.tags.is_empty()); | ||
| 1062 | if paragraph.style.is_some() | ||
| 1063 | || !paragraph.lists.is_empty() | ||
| 1064 | || !paragraph.tags.is_empty() | ||
| 1065 | || text_tags | ||
| 1066 | || paragraph.collapsed | ||
| 1067 | { | ||
| 1068 | return Err(OpError::Unsupported( | ||
| 1069 | "Set styles, lists, tags and collapse state with their own ops", | ||
| 1070 | )); | ||
| 1071 | } | ||
| 1072 | if paragraph.media != Default::default() { | ||
| 1073 | return Err(OpError::Unsupported( | ||
| 1074 | "Recording annotations are made by OneNote while it records", | ||
| 1075 | )); | ||
| 1076 | } | ||
| 1077 | match &paragraph.content { | ||
| 1078 | ParagraphContent::Text(text) if text.date_field.is_some() => Err(OpError::Unsupported( | ||
| 1079 | "New paragraphs contain plain text without fields", | ||
| 1080 | )), | ||
| 1081 | ParagraphContent::Table(table) => { | ||
| 1082 | if !table.tags.is_empty() { | ||
| 1083 | return Err(OpError::Unsupported("Set a table's tags with their own op")); | ||
| 1084 | } | ||
| 1085 | for paragraph in table.rows.iter().flat_map(|row| &row.cells).flat_map(|cell| &cell.paragraphs) | ||
| 1086 | { | ||
| 1087 | bare(paragraph)?; | ||
| 1088 | } | ||
| 1089 | Ok(()) | ||
| 1090 | } | ||
| 1091 | ParagraphContent::Attachment(attachment) if attachment.recording.is_some() => Err( | ||
| 1092 | OpError::Unsupported("Recordings are captured by OneNote, not inserted"), | ||
| 1093 | ), | ||
| 1094 | _ => Ok(()), | ||
| 1095 | } | ||
| 1096 | } | ||
| 1097 | |||
| 1098 | /// The field code OneNote stores before a hyperlink's label. | ||
| 1099 | fn field_code(target: &str) -> Result<String, Error> { | ||
| 1100 | if target.is_empty() || target.contains(['"', '\0', '\r', '\n', '\u{fddf}']) { | ||
| 1101 | return Err(invalid("Choose a link target without quotes or line breaks")); | ||
| 1102 | } | ||
| 1103 | Ok(format!("\u{fddf}HYPERLINK \"{target}\"")) | ||
| 1104 | } | ||
| 1105 | |||
| 1106 | /// The text edits making `range` of `text` a hyperlink to `target`: the field code | ||
| 1107 | /// inserted hidden before the label, both flagged as link runs as OneNote stores them. With | ||
| 1108 | /// `None` or an existing link, the field code before the label is removed first. | ||
| 1109 | pub(crate) fn link_ops( | ||
| 1110 | text: &Paragraph, | ||
| 1111 | id: ExGuid, | ||
| 1112 | range: Range<u32>, | ||
| 1113 | target: Option<&str>, | ||
| 1114 | ) -> Result<Vec<PageOp>, Error> { | ||
| 1115 | if range.is_empty() { | ||
| 1116 | return Err(invalid("A link needs a label")); | ||
| 1117 | } | ||
| 1118 | let at = |offset: u32| super::lower::format_in(text, offset); | ||
| 1119 | let mut ops = Vec::new(); | ||
| 1120 | let mut label = range.clone(); | ||
| 1121 | // A field code is a hidden link run ending where the label starts. | ||
| 1122 | let start = text.byte_offset(range.start)?; | ||
| 1123 | let spans = text.spans(); | ||
| 1124 | let code = spans.iter().enumerate().find_map(|(i, span)| { | ||
| 1125 | let from = if i == 0 { 0 } else { spans[i - 1].end }; | ||
| 1126 | (span.end == start | ||
| 1127 | && span.format.hidden == Some(true) | ||
| 1128 | && span.format.hyperlink == Some(true) | ||
| 1129 | && text.text()[from..span.end].starts_with('\u{fddf}')) | ||
| 1130 | .then_some(from..span.end) | ||
| 1131 | }); | ||
| 1132 | if let Some(code) = code { | ||
| 1133 | let code = text.utf16_offset(code.start)?..text.utf16_offset(code.end)?; | ||
| 1134 | let length = code.end - code.start; | ||
| 1135 | ops.push(PageOp::Text { | ||
| 1136 | text: id, | ||
| 1137 | range: code.clone(), | ||
| 1138 | with: String::new(), | ||
| 1139 | }); | ||
| 1140 | label = label.start - length..label.end - length; | ||
| 1141 | } else if target.is_none() { | ||
| 1142 | return Err(invalid("The range is no link")); | ||
| 1143 | } | ||
| 1144 | let base = at(range.start)?.clone(); | ||
| 1145 | match target { | ||
| 1146 | Some(target) => { | ||
| 1147 | let code = field_code(target)?; | ||
| 1148 | let length = code.encode_utf16().count() as u32; | ||
| 1149 | ops.push(PageOp::Text { | ||
| 1150 | text: id, | ||
| 1151 | range: label.start..label.start, | ||
| 1152 | with: code, | ||
| 1153 | }); | ||
| 1154 | let mut set = vec![ | ||
| 1155 | TextAttribute::Hyperlink(true), | ||
| 1156 | TextAttribute::HyperlinkLabel(true), | ||
| 1157 | ]; | ||
| 1158 | if base.hidden != Some(true) { | ||
| 1159 | ops.push(PageOp::Format { | ||
| 1160 | text: id, | ||
| 1161 | range: label.start..label.start + length, | ||
| 1162 | set: [set.clone(), vec![TextAttribute::Hidden(true)]].concat(), | ||
| 1163 | clear: Vec::new(), | ||
| 1164 | }); | ||
| 1165 | } | ||
| 1166 | set.retain(|attribute| match attribute { | ||
| 1167 | TextAttribute::Hyperlink(_) => base.hyperlink != Some(true), | ||
| 1168 | _ => base.hyperlink_label != Some(true), | ||
| 1169 | }); | ||
| 1170 | if !set.is_empty() || base.hidden == Some(true) { | ||
| 1171 | ops.push(PageOp::Format { | ||
| 1172 | text: id, | ||
| 1173 | range: label.start + length..label.end + length, | ||
| 1174 | set, | ||
| 1175 | clear: Vec::new(), | ||
| 1176 | }); | ||
| 1177 | } | ||
| 1178 | } | ||
| 1179 | None => ops.push(PageOp::Format { | ||
| 1180 | text: id, | ||
| 1181 | range: label, | ||
| 1182 | set: Vec::new(), | ||
| 1183 | clear: vec![TextProperty::Hyperlink, TextProperty::HyperlinkLabel], | ||
| 1184 | }), | ||
| 1185 | } | ||
| 1186 | ops.retain(|op| !matches!(op, PageOp::Format { set, clear, .. } if set.is_empty() && clear.is_empty())); | ||
| 1187 | Ok(ops) | ||
| 1188 | } | ||
crates/onestore/src/op/content.rs created+746| ... | @@ -0,0 +1,746 @@ | ||
| 1 | //! Paragraph and page content other than text: pictures, attachments, ink and equations, | ||
| 2 | //! written as OneNote 2010 stores them. | ||
| 3 | |||
| 4 | use super::Values; | ||
| 5 | use crate::{ | ||
| 6 | Error, ExGuid, | ||
| 7 | active::{ActivePage, Changes}, | ||
| 8 | document::{Format, Layout}, | ||
| 9 | page::{Attachment, Image, Ink, InkStroke, Paragraph}, | ||
| 10 | write::PropertyObject, | ||
| 11 | }; | ||
| 12 | use std::collections::BTreeMap; | ||
| 13 | |||
| 14 | fn invalid(message: &'static str) -> Error { | ||
| 15 | Error { offset: 0, message } | ||
| 16 | } | ||
| 17 | |||
| 18 | /// The `<ifndf>{GUID}` form a file-data declaration uses to name a payload in the file-data store. | ||
| 19 | fn payload_reference(guid: [u8; 16]) -> String { | ||
| 20 | let id = ExGuid { guid, n: 0 }.to_string(); | ||
| 21 | format!("<ifndf>{}", id.split(',').next().unwrap()) | ||
| 22 | } | ||
| 23 | |||
| 24 | /// The compact child references a page object holds. | ||
| 25 | fn page_children(object: &PropertyObject) -> Result<Vec<u8>, Error> { | ||
| 26 | let properties = crate::PropertySets::parse(&object.bytes)?; | ||
| 27 | match properties.sets[0] | ||
| 28 | .iter() | ||
| 29 | .find(|p| p.id == 0x24001c20) | ||
| 30 | .map(|p| &p.value) | ||
| 31 | { | ||
| 32 | Some(crate::Value::References { compact_ids, .. }) => Ok(compact_ids.to_vec()), | ||
| 33 | None => Ok(Vec::new()), | ||
| 34 | _ => Err(invalid("The page has an invalid child list")), | ||
| 35 | } | ||
| 36 | } | ||
| 37 | |||
| 38 | /// Makes `object` the content of paragraph `holder`, or a child of the page after its others. | ||
| 39 | fn hold( | ||
| 40 | active: &ActivePage<'_>, | ||
| 41 | changed: &mut Changes, | ||
| 42 | object: ExGuid, | ||
| 43 | holder: Option<ExGuid>, | ||
| 44 | modified: &[u8; 4], | ||
| 45 | ) -> Result<(), Error> { | ||
| 46 | let raw = &active.live.revision; | ||
| 47 | match holder { | ||
| 48 | Some(holder) => { | ||
| 49 | let mut paragraph = PropertyObject::from_object(&raw.objects[&holder])?; | ||
| 50 | let content = paragraph.reference(object)?; | ||
| 51 | paragraph.set(&[(0x24001c1f, &content), (0x14001d7a, modified)])?; | ||
| 52 | changed.insert(holder, paragraph); | ||
| 53 | } | ||
| 54 | None => { | ||
| 55 | let [page] = active.pages[..] else { | ||
| 56 | return Err(invalid("Choose an object space containing one active page")); | ||
| 57 | }; | ||
| 58 | let mut node = PropertyObject::from_object(&raw.objects[&page])?; | ||
| 59 | let mut children = page_children(&node)?; | ||
| 60 | children.extend(node.reference(object)?); | ||
| 61 | node.set(&[(0x24001c20, &children), (0x14001d7a, modified)])?; | ||
| 62 | changed.insert(page, node); | ||
| 63 | } | ||
| 64 | } | ||
| 65 | Ok(()) | ||
| 66 | } | ||
| 67 | |||
| 68 | /// The character-style properties a span format sets; paragraph-level fields stay on the | ||
| 69 | /// paragraph style. | ||
| 70 | pub(crate) fn style_values(format: &Format) -> Values { | ||
| 71 | let mut values = Values::new(); | ||
| 72 | for (id, flag) in [ | ||
| 73 | (0x08001c04, format.bold), | ||
| 74 | (0x08001c05, format.italic), | ||
| 75 | (0x08001c06, format.underline), | ||
| 76 | (0x08001c07, format.strike), | ||
| 77 | (0x08001c08, format.superscript), | ||
| 78 | (0x08001c09, format.subscript), | ||
| 79 | (0x08001e16, format.hidden), | ||
| 80 | (0x08001e14, format.hyperlink), | ||
| 81 | (0x08001e19, format.hyperlink_label), | ||
| 82 | (0x08003401, format.math), | ||
| 83 | (0x08001e22, format.embedded_object), | ||
| 84 | ] { | ||
| 85 | if let Some(flag) = flag { | ||
| 86 | values.push((id | (u32::from(flag) << 31), Vec::new())); | ||
| 87 | } | ||
| 88 | } | ||
| 89 | if let Some(font) = &format.font { | ||
| 90 | values.push((0x1c001c0a, crate::create::string(font))); | ||
| 91 | } | ||
| 92 | if let Some(size) = format.font_size { | ||
| 93 | values.push(( | ||
| 94 | 0x10001c0b, | ||
| 95 | ((size * 2.0).round() as u16).to_le_bytes().to_vec(), | ||
| 96 | )); | ||
| 97 | } | ||
| 98 | if let Some(color) = format.color { | ||
| 99 | values.push((0x14001c0c, color.to_le_bytes().to_vec())); | ||
| 100 | } | ||
| 101 | if let Some(highlight) = format.highlight { | ||
| 102 | values.push((0x14001c0d, highlight.to_le_bytes().to_vec())); | ||
| 103 | } | ||
| 104 | if let Some(language) = format.language { | ||
| 105 | values.push((0x14001c3b, language.to_le_bytes().to_vec())); | ||
| 106 | } | ||
| 107 | values | ||
| 108 | } | ||
| 109 | |||
| 110 | /// The indentation table, in points, OneNote gives each outline and table cell it creates. | ||
| 111 | pub(crate) const NATIVE_INDENTS: [f32; 4] = [18.0, 0.0, 27.0, 27.0]; | ||
| 112 | |||
| 113 | /// A native measurement array: a count byte, `header - 1` reserved bytes, then each value | ||
| 114 | /// in half-inch units. | ||
| 115 | pub(crate) fn measurement_bytes(values: &[f32], header: usize) -> Result<Vec<u8>, Error> { | ||
| 116 | let count = u8::try_from(values.len()) | ||
| 117 | .map_err(|_| invalid("A measurement array exceeds the document range"))?; | ||
| 118 | let mut bytes = vec![0u8; header]; | ||
| 119 | bytes[0] = count; | ||
| 120 | for value in values { | ||
| 121 | if !value.is_finite() { | ||
| 122 | return Err(invalid("A measurement must be finite")); | ||
| 123 | } | ||
| 124 | bytes.extend_from_slice(&(value / 36.0).to_le_bytes()); | ||
| 125 | } | ||
| 126 | Ok(bytes) | ||
| 127 | } | ||
| 128 | |||
| 129 | /// A stored picture keeps what `Picture` cannot change. | ||
| 130 | pub(crate) fn picture_fixed_fields(stored: &Image, image: &Image) -> Result<(), Error> { | ||
| 131 | if stored.id != image.id | ||
| 132 | || stored.bytes != image.bytes | ||
| 133 | || stored.size != image.size | ||
| 134 | || stored.background != image.background | ||
| 135 | { | ||
| 136 | return Err(invalid( | ||
| 137 | "A stored picture keeps its payload, intrinsic size and background state", | ||
| 138 | )); | ||
| 139 | } | ||
| 140 | Ok(()) | ||
| 141 | } | ||
| 142 | |||
| 143 | /// Position and displayed-size properties that take `layout` from `stored`, and the ones | ||
| 144 | /// to remove. | ||
| 145 | fn layout_values(stored: &Layout, layout: &Layout) -> Result<(Values, Vec<u32>), Error> { | ||
| 146 | let mut values = Values::new(); | ||
| 147 | let mut removed = Vec::new(); | ||
| 148 | if (layout.x, layout.y) != (stored.x, stored.y) { | ||
| 149 | let (Some(x), Some(y)) = (layout.x, layout.y) else { | ||
| 150 | return Err(invalid("A picture position needs both coordinates")); | ||
| 151 | }; | ||
| 152 | if !(x.is_finite() && y.is_finite()) { | ||
| 153 | return Err(invalid("A picture position must be finite")); | ||
| 154 | } | ||
| 155 | values.push((0x14001c14, (x / 36.0).to_le_bytes().to_vec())); | ||
| 156 | values.push((0x14001c15, (y / 36.0).to_le_bytes().to_vec())); | ||
| 157 | } | ||
| 158 | if ( | ||
| 159 | layout.max_width, | ||
| 160 | layout.max_height, | ||
| 161 | layout.width_set_by_user, | ||
| 162 | ) != ( | ||
| 163 | stored.max_width, | ||
| 164 | stored.max_height, | ||
| 165 | stored.width_set_by_user, | ||
| 166 | ) { | ||
| 167 | match (layout.max_width, layout.max_height) { | ||
| 168 | (Some(width), Some(height)) => { | ||
| 169 | if !(width.is_finite() && height.is_finite() && width > 0.0 && height > 0.0) { | ||
| 170 | return Err(invalid("Picture size must be positive")); | ||
| 171 | } | ||
| 172 | values.push((0x14001c1b, (width / 36.0).to_le_bytes().to_vec())); | ||
| 173 | values.push((0x14001c1c, (height / 36.0).to_le_bytes().to_vec())); | ||
| 174 | let user_set = u32::from(layout.width_set_by_user == Some(true)); | ||
| 175 | values.push((0x08001cbd | (user_set << 31), Vec::new())); | ||
| 176 | } | ||
| 177 | (None, None) => removed.extend([0x14001c1b, 0x14001c1c, 0x08001cbd]), | ||
| 178 | _ => return Err(invalid("A picture size needs both dimensions")), | ||
| 179 | } | ||
| 180 | } | ||
| 181 | if layout.reserved_width != stored.reserved_width { | ||
| 182 | return Err(invalid("A picture has no reserved width")); | ||
| 183 | } | ||
| 184 | Ok((values, removed)) | ||
| 185 | } | ||
| 186 | |||
| 187 | fn icon_size(values: &mut Values, size: Option<[f32; 2]>, message: &'static str) -> Result<(), Error> { | ||
| 188 | if let Some([width, height]) = size { | ||
| 189 | if !(width.is_finite() && height.is_finite() && width > 0.0 && height > 0.0) { | ||
| 190 | return Err(invalid(message)); | ||
| 191 | } | ||
| 192 | values.push((0x140034cd, (width / 36.0).to_le_bytes().to_vec())); | ||
| 193 | values.push((0x140034ce, (height / 36.0).to_le_bytes().to_vec())); | ||
| 194 | } | ||
| 195 | Ok(()) | ||
| 196 | } | ||
| 197 | |||
| 198 | /// What OneNote stores for an inserted picture: a file-data object declaring payload | ||
| 199 | /// `payload` by identity and extension, and picture object `id` that paragraph `holder` | ||
| 200 | /// holds as content or the page lists as a child. The caller embeds the payload. | ||
| 201 | pub(crate) fn picture_changes( | ||
| 202 | active: &ActivePage<'_>, | ||
| 203 | image: &Image, | ||
| 204 | id: ExGuid, | ||
| 205 | file: ExGuid, | ||
| 206 | payload: [u8; 16], | ||
| 207 | holder: Option<ExGuid>, | ||
| 208 | ) -> Result<Changes, Error> { | ||
| 209 | let Some(bytes) = &image.bytes else { | ||
| 210 | return Err(invalid("A new picture needs its payload")); | ||
| 211 | }; | ||
| 212 | let extension = match bytes.as_ref() { | ||
| 213 | [0x89, b'P', b'N', b'G', ..] => ".png", | ||
| 214 | [0xff, 0xd8, 0xff, ..] => ".jpg", | ||
| 215 | [b'G', b'I', b'F', b'8', ..] => ".gif", | ||
| 216 | [b'B', b'M', ..] => ".bmp", | ||
| 217 | _ => return Err(invalid("Choose a PNG, JPEG, GIF or BMP picture")), | ||
| 218 | }; | ||
| 219 | let modified = crate::create::current_timestamps()?.0.to_le_bytes(); | ||
| 220 | let mut values: Values = vec![(0x14001d7a, modified.to_vec())]; | ||
| 221 | if let Some([width, height]) = image.size { | ||
| 222 | if !(width.is_finite() && height.is_finite() && width > 0.0 && height > 0.0) { | ||
| 223 | return Err(invalid("Picture size must be positive")); | ||
| 224 | } | ||
| 225 | values.push((0x140034cd, (width / 36.0).to_le_bytes().to_vec())); | ||
| 226 | values.push((0x140034ce, (height / 36.0).to_le_bytes().to_vec())); | ||
| 227 | } | ||
| 228 | values.extend(layout_values(&Default::default(), &image.layout)?.0); | ||
| 229 | if let Some(alt) = &image.alt { | ||
| 230 | values.push((0x1c001e58, crate::create::string(alt))); | ||
| 231 | } | ||
| 232 | if image.background { | ||
| 233 | values.push((0x08001d13 | (1 << 31), Vec::new())); | ||
| 234 | } | ||
| 235 | values.push((0x14001c3b, 0x409_u32.to_le_bytes().to_vec())); | ||
| 236 | values.push((0x08001d85, Vec::new())); | ||
| 237 | let mut changed = BTreeMap::new(); | ||
| 238 | changed.insert( | ||
| 239 | file, | ||
| 240 | PropertyObject::file(file, &payload_reference(payload), extension)?, | ||
| 241 | ); | ||
| 242 | let mut picture = PropertyObject { | ||
| 243 | jcid: 0x60011, | ||
| 244 | bytes: crate::create::properties(&values)?, | ||
| 245 | global_ids: std::sync::Arc::new(BTreeMap::from([(0, id.guid)])), | ||
| 246 | }; | ||
| 247 | picture.reference(id)?; | ||
| 248 | let container = picture.reference(file)?; | ||
| 249 | picture.set(&[(0x20001c3f, &container)])?; | ||
| 250 | changed.insert(id, picture); | ||
| 251 | hold(active, &mut changed, id, holder, &modified)?; | ||
| 252 | Ok(changed) | ||
| 253 | } | ||
| 254 | |||
| 255 | /// A moved, resized or described picture as OneNote stores one: the position, the layout | ||
| 256 | /// width and height with the user flag and the description on the picture object, leaving | ||
| 257 | /// the intrinsic size alone. | ||
| 258 | pub(crate) fn picture_edit_changes( | ||
| 259 | active: &ActivePage<'_>, | ||
| 260 | object: ExGuid, | ||
| 261 | stored: (&Layout, &Option<String>), | ||
| 262 | layout: &Layout, | ||
| 263 | alt: &Option<String>, | ||
| 264 | ) -> Result<Changes, Error> { | ||
| 265 | let mut values: Values = vec![( | ||
| 266 | 0x14001d7a, | ||
| 267 | crate::create::current_timestamps()? | ||
| 268 | .0 | ||
| 269 | .to_le_bytes() | ||
| 270 | .to_vec(), | ||
| 271 | )]; | ||
| 272 | let (layout, mut removed) = layout_values(stored.0, layout)?; | ||
| 273 | values.extend(layout); | ||
| 274 | if alt != stored.1 { | ||
| 275 | match alt { | ||
| 276 | Some(alt) => values.push((0x1c001e58, crate::create::string(alt))), | ||
| 277 | None => removed.push(0x1c001e58), | ||
| 278 | } | ||
| 279 | } | ||
| 280 | let raw = &active.live.revision; | ||
| 281 | let mut picture = PropertyObject::from_object(&raw.objects[&object])?; | ||
| 282 | picture.remove(&removed)?; | ||
| 283 | let values: Vec<(u32, &[u8])> = values | ||
| 284 | .iter() | ||
| 285 | .map(|(id, bytes)| (*id, bytes.as_slice())) | ||
| 286 | .collect(); | ||
| 287 | picture.set(&values)?; | ||
| 288 | Ok(BTreeMap::from([(object, picture)])) | ||
| 289 | } | ||
| 290 | |||
| 291 | /// Identities of a new attachment's objects and payloads. | ||
| 292 | pub(crate) struct AttachmentIds { | ||
| 293 | pub object: ExGuid, | ||
| 294 | pub file: ExGuid, | ||
| 295 | pub payload: [u8; 16], | ||
| 296 | /// The preview icon's payload and file-data object, when it has one. | ||
| 297 | pub preview: Option<([u8; 16], ExGuid)>, | ||
| 298 | } | ||
| 299 | |||
| 300 | /// What OneNote stores for an inserted file: an embedded-file container declaring the | ||
| 301 | /// payload and an attachment object naming the file, which paragraph `holder` holds as | ||
| 302 | /// content. The caller embeds the payloads. | ||
| 303 | pub(crate) fn attachment_changes( | ||
| 304 | active: &ActivePage<'_>, | ||
| 305 | attachment: &Attachment, | ||
| 306 | ids: &AttachmentIds, | ||
| 307 | holder: ExGuid, | ||
| 308 | ) -> Result<Changes, Error> { | ||
| 309 | let name = attachment.filename.as_str(); | ||
| 310 | if name.is_empty() || name.contains(['\0', '/', '\\']) { | ||
| 311 | return Err(invalid( | ||
| 312 | "An attachment needs a file name without path separators", | ||
| 313 | )); | ||
| 314 | } | ||
| 315 | if attachment.recording.is_some() { | ||
| 316 | return Err(invalid("Recordings are captured by OneNote, not inserted")); | ||
| 317 | } | ||
| 318 | if attachment.bytes.is_none() { | ||
| 319 | return Err(invalid("A new attachment needs its payload")); | ||
| 320 | } | ||
| 321 | if attachment | ||
| 322 | .preview | ||
| 323 | .as_ref() | ||
| 324 | .is_some_and(|icon| !icon.starts_with(&[0x89, b'P', b'N', b'G'])) | ||
| 325 | { | ||
| 326 | return Err(invalid("An attachment preview is a PNG icon")); | ||
| 327 | } | ||
| 328 | let extension = name | ||
| 329 | .rfind('.') | ||
| 330 | .filter(|dot| *dot > 0) | ||
| 331 | .map(|dot| &name[dot..]) | ||
| 332 | .unwrap_or(""); | ||
| 333 | let modified = crate::create::current_timestamps()?.0.to_le_bytes(); | ||
| 334 | let mut values: Values = vec![(0x14001d7a, modified.to_vec())]; | ||
| 335 | icon_size(&mut values, attachment.size, "Attachment icon size must be positive")?; | ||
| 336 | values.push((0x14001c3b, 0x409_u32.to_le_bytes().to_vec())); | ||
| 337 | values.push((0x10001cfe, 0x409_u16.to_le_bytes().to_vec())); | ||
| 338 | values.push((0x1c001dcf, vec![0; 32])); | ||
| 339 | values.push(( | ||
| 340 | 0x1c001d61, | ||
| 341 | [16u32, 1, 0, 0, 0] | ||
| 342 | .iter() | ||
| 343 | .flat_map(|v| v.to_le_bytes()) | ||
| 344 | .collect(), | ||
| 345 | )); | ||
| 346 | values.push((0x14001c3e, 1u32.to_le_bytes().to_vec())); | ||
| 347 | values.push((0x14001c84, 1u32.to_le_bytes().to_vec())); | ||
| 348 | values.push((0x1c001c22, crate::create::string(name))); | ||
| 349 | values.push((0x1c001d9c, crate::create::string(name))); | ||
| 350 | if let Some(path) = &attachment.source_path { | ||
| 351 | values.push((0x1c001d9d, crate::create::string(path))); | ||
| 352 | } | ||
| 353 | let mut changed = BTreeMap::new(); | ||
| 354 | let mut file = PropertyObject::file(ids.file, &payload_reference(ids.payload), extension)?; | ||
| 355 | file.jcid = crate::write::EMBEDDED_FILE_JCID; | ||
| 356 | changed.insert(ids.file, file); | ||
| 357 | let mut node = PropertyObject { | ||
| 358 | jcid: 0x60035, | ||
| 359 | bytes: crate::create::properties(&values)?, | ||
| 360 | global_ids: std::sync::Arc::new(BTreeMap::from([(0, ids.object.guid)])), | ||
| 361 | }; | ||
| 362 | node.reference(ids.object)?; | ||
| 363 | let container = node.reference(ids.file)?; | ||
| 364 | node.set(&[(0x20001d9b, &container)])?; | ||
| 365 | if let Some((payload, icon)) = ids.preview { | ||
| 366 | changed.insert( | ||
| 367 | icon, | ||
| 368 | PropertyObject::file(icon, &payload_reference(payload), ".png")?, | ||
| 369 | ); | ||
| 370 | let reference = node.reference(icon)?; | ||
| 371 | node.set(&[(0x20001c3f, &reference)])?; | ||
| 372 | } | ||
| 373 | changed.insert(ids.object, node); | ||
| 374 | hold(active, &mut changed, ids.object, Some(holder), &modified)?; | ||
| 375 | Ok(changed) | ||
| 376 | } | ||
| 377 | |||
| 378 | /// A stored attachment keeps its payload and preview; its shown name, recorded source | ||
| 379 | /// path and icon size change in place, as OneNote's rename does. | ||
| 380 | pub(crate) fn attachment_edit_changes( | ||
| 381 | active: &ActivePage<'_>, | ||
| 382 | stored: &Attachment, | ||
| 383 | attachment: &Attachment, | ||
| 384 | ) -> Result<Changes, Error> { | ||
| 385 | let mut values: Values = vec![( | ||
| 386 | 0x14001d7a, | ||
| 387 | crate::create::current_timestamps()? | ||
| 388 | .0 | ||
| 389 | .to_le_bytes() | ||
| 390 | .to_vec(), | ||
| 391 | )]; | ||
| 392 | if attachment.recording != stored.recording { | ||
| 393 | return Err(invalid("A recording stays the recording OneNote captured")); | ||
| 394 | } | ||
| 395 | let name = attachment.filename.as_str(); | ||
| 396 | if name.is_empty() || name.contains(['\0', '/', '\\']) { | ||
| 397 | return Err(invalid( | ||
| 398 | "An attachment needs a file name without path separators", | ||
| 399 | )); | ||
| 400 | } | ||
| 401 | let mut removed = Vec::new(); | ||
| 402 | if attachment.filename != stored.filename { | ||
| 403 | let name = crate::create::string(&attachment.filename); | ||
| 404 | values.push((0x1c001c22, name.clone())); | ||
| 405 | values.push((0x1c001d9c, name)); | ||
| 406 | } | ||
| 407 | if attachment.source_path != stored.source_path { | ||
| 408 | match &attachment.source_path { | ||
| 409 | Some(path) => values.push((0x1c001d9d, crate::create::string(path))), | ||
| 410 | None => removed.push(0x1c001d9d), | ||
| 411 | } | ||
| 412 | } | ||
| 413 | if attachment.size != stored.size { | ||
| 414 | match attachment.size { | ||
| 415 | Some(_) => icon_size(&mut values, attachment.size, "Attachment icon size must be positive")?, | ||
| 416 | None => removed.extend([0x140034cd, 0x140034ce]), | ||
| 417 | } | ||
| 418 | } | ||
| 419 | let object = stored.id; | ||
| 420 | let raw = &active.live.revision; | ||
| 421 | let mut node = PropertyObject::from_object(&raw.objects[&object])?; | ||
| 422 | node.remove(&removed)?; | ||
| 423 | let values: Vec<(u32, &[u8])> = values | ||
| 424 | .iter() | ||
| 425 | .map(|(id, bytes)| (*id, bytes.as_slice())) | ||
| 426 | .collect(); | ||
| 427 | node.set(&values)?; | ||
| 428 | Ok(BTreeMap::from([(object, node)])) | ||
| 429 | } | ||
| 430 | |||
| 431 | /// Creates stroke objects (numbered from `first`) and one drawing-attribute object per | ||
| 432 | /// distinct pen; returns the compact references for the data node's stroke list. | ||
| 433 | fn write_strokes( | ||
| 434 | changed: &mut Changes, | ||
| 435 | data_object: &mut PropertyObject, | ||
| 436 | strokes: &[(ExGuid, &InkStroke)], | ||
| 437 | first: usize, | ||
| 438 | filetime: u64, | ||
| 439 | ) -> Result<Vec<u8>, Error> { | ||
| 440 | let mut styles: Vec<(InkPen, ExGuid)> = Vec::new(); | ||
| 441 | let mut references = Vec::new(); | ||
| 442 | for (offset, (stroke_id, stroke)) in strokes.iter().enumerate() { | ||
| 443 | let pen = InkPen::of(stroke); | ||
| 444 | let style = match styles.iter().find(|(known, _)| *known == pen) { | ||
| 445 | Some((_, id)) => *id, | ||
| 446 | None => { | ||
| 447 | let id = ExGuid { | ||
| 448 | guid: crate::write::fresh_guid()?, | ||
| 449 | n: 1, | ||
| 450 | }; | ||
| 451 | let mut style = PropertyObject { | ||
| 452 | jcid: 0x120048, | ||
| 453 | bytes: crate::create::properties(&pen.values())?, | ||
| 454 | global_ids: std::sync::Arc::new(BTreeMap::from([(0, id.guid)])), | ||
| 455 | }; | ||
| 456 | style.reference(id)?; | ||
| 457 | changed.insert(id, style); | ||
| 458 | styles.push((pen, id)); | ||
| 459 | id | ||
| 460 | } | ||
| 461 | }; | ||
| 462 | let mut object = PropertyObject { | ||
| 463 | jcid: 0x20047, | ||
| 464 | bytes: crate::create::properties(&stroke_values( | ||
| 465 | stroke, | ||
| 466 | (first + offset) as u32 + 1, | ||
| 467 | filetime, | ||
| 468 | )?)?, | ||
| 469 | global_ids: std::sync::Arc::new(BTreeMap::from([(0, stroke_id.guid)])), | ||
| 470 | }; | ||
| 471 | object.reference(*stroke_id)?; | ||
| 472 | let style_reference = object.reference(style)?; | ||
| 473 | object.set(&[(0x20003409, &style_reference)])?; | ||
| 474 | changed.insert(*stroke_id, object); | ||
| 475 | references.extend(data_object.reference(*stroke_id)?); | ||
| 476 | } | ||
| 477 | Ok(references) | ||
| 478 | } | ||
| 479 | |||
| 480 | /// The drawing attributes OneNote shares between strokes drawn with the same pen. | ||
| 481 | #[derive(PartialEq)] | ||
| 482 | struct InkPen { | ||
| 483 | width: u32, | ||
| 484 | height: u32, | ||
| 485 | color: Option<u32>, | ||
| 486 | transparency: Option<u8>, | ||
| 487 | pen_tip: Option<u8>, | ||
| 488 | } | ||
| 489 | |||
| 490 | impl InkPen { | ||
| 491 | fn of(stroke: &InkStroke) -> Self { | ||
| 492 | Self { | ||
| 493 | width: (stroke.width * 2540.0 / 72.0).to_bits(), | ||
| 494 | height: (stroke.height * 2540.0 / 72.0).to_bits(), | ||
| 495 | color: stroke.color, | ||
| 496 | transparency: stroke.transparency, | ||
| 497 | pen_tip: stroke.pen_tip, | ||
| 498 | } | ||
| 499 | } | ||
| 500 | |||
| 501 | fn values(&self) -> Values { | ||
| 502 | let mut values: Values = vec![ | ||
| 503 | (0x1c00340a, crate::page::ink::DIMENSIONS.to_vec()), | ||
| 504 | (0x1400340c, self.height.to_le_bytes().to_vec()), | ||
| 505 | (0x1400340d, self.width.to_le_bytes().to_vec()), | ||
| 506 | ]; | ||
| 507 | if let Some(color) = self.color { | ||
| 508 | values.push((0x1400340f, color.to_le_bytes().to_vec())); | ||
| 509 | } | ||
| 510 | if let Some(transparency) = self.transparency { | ||
| 511 | values.push((0x0c003414, vec![transparency])); | ||
| 512 | } | ||
| 513 | if let Some(tip) = self.pen_tip { | ||
| 514 | values.push((0x0c003412, vec![tip])); | ||
| 515 | } | ||
| 516 | values | ||
| 517 | } | ||
| 518 | } | ||
| 519 | |||
| 520 | fn stroke_values(stroke: &InkStroke, index: u32, filetime: u64) -> Result<Values, Error> { | ||
| 521 | if stroke.points.is_empty() { | ||
| 522 | return Err(invalid("A stroke needs at least one point")); | ||
| 523 | } | ||
| 524 | if stroke | ||
| 525 | .points | ||
| 526 | .iter() | ||
| 527 | .any(|p| !p[0].is_finite() || !p[1].is_finite()) | ||
| 528 | || !(stroke.width.is_finite() && stroke.height.is_finite()) | ||
| 529 | || stroke.width <= 0.0 | ||
| 530 | || stroke.height <= 0.0 | ||
| 531 | { | ||
| 532 | return Err(invalid( | ||
| 533 | "Stroke points and pen size must be finite and positive", | ||
| 534 | )); | ||
| 535 | } | ||
| 536 | let left = stroke | ||
| 537 | .points | ||
| 538 | .iter() | ||
| 539 | .map(|p| p[0]) | ||
| 540 | .fold(f32::INFINITY, f32::min); | ||
| 541 | let top = stroke | ||
| 542 | .points | ||
| 543 | .iter() | ||
| 544 | .map(|p| p[1]) | ||
| 545 | .fold(f32::INFINITY, f32::min); | ||
| 546 | let mut origin = ((left - stroke.width / 2.0) / 36.0).to_le_bytes().to_vec(); | ||
| 547 | origin.extend_from_slice(&((top - stroke.height / 2.0) / 36.0).to_le_bytes()); | ||
| 548 | Ok(vec![ | ||
| 549 | (0x1c00340b, stroke.packet()), | ||
| 550 | (0x14003419, index.to_le_bytes().to_vec()), | ||
| 551 | (0x1000341b, 0x409_u16.to_le_bytes().to_vec()), | ||
| 552 | (0x0c00341c, vec![0]), | ||
| 553 | (0x1c00341a, crate::write::fresh_guid()?.to_vec()), | ||
| 554 | (0x1c00341d, filetime.to_le_bytes().to_vec()), | ||
| 555 | (0x1c00345b, origin), | ||
| 556 | ]) | ||
| 557 | } | ||
| 558 | |||
| 559 | /// New ink as OneNote 2010 stores a drawing: container `id` that the page lists as a child | ||
| 560 | /// (or paragraph `holder` holds as content), its data node `data` listing the strokes, each | ||
| 561 | /// stroke's packet and half-inch origin, and one drawing-attribute object per distinct pen. | ||
| 562 | pub(crate) fn ink_changes( | ||
| 563 | active: &ActivePage<'_>, | ||
| 564 | ink: &Ink, | ||
| 565 | id: ExGuid, | ||
| 566 | data: ExGuid, | ||
| 567 | strokes: &[(ExGuid, &InkStroke)], | ||
| 568 | holder: Option<ExGuid>, | ||
| 569 | ) -> Result<Changes, Error> { | ||
| 570 | if !ink.groups.is_empty() { | ||
| 571 | return Err(invalid("New ink holds strokes, not nested groups")); | ||
| 572 | } | ||
| 573 | if ink.strokes.is_empty() { | ||
| 574 | return Err(invalid("New ink needs at least one stroke")); | ||
| 575 | } | ||
| 576 | if ink.layout != Default::default() { | ||
| 577 | return Err(invalid("Ink positions come from its strokes")); | ||
| 578 | } | ||
| 579 | let (modified, filetime) = crate::create::current_timestamps()?; | ||
| 580 | let modified = modified.to_le_bytes(); | ||
| 581 | let mut changed = BTreeMap::new(); | ||
| 582 | let mut data_object = PropertyObject { | ||
| 583 | jcid: 0x2003b, | ||
| 584 | bytes: crate::create::properties(&[])?, | ||
| 585 | global_ids: std::sync::Arc::new(BTreeMap::from([(0, data.guid)])), | ||
| 586 | }; | ||
| 587 | data_object.reference(data)?; | ||
| 588 | let references = write_strokes(&mut changed, &mut data_object, strokes, 0, filetime)?; | ||
| 589 | data_object.set(&[(0x24003416, &references)])?; | ||
| 590 | changed.insert(data, data_object); | ||
| 591 | let mut object = PropertyObject { | ||
| 592 | jcid: 0x60014, | ||
| 593 | bytes: crate::create::properties(&[ | ||
| 594 | (0x14001d7a, modified.to_vec()), | ||
| 595 | (0x14001d4e, 1u32.to_le_bytes().to_vec()), | ||
| 596 | ])?, | ||
| 597 | global_ids: std::sync::Arc::new(BTreeMap::from([(0, id.guid)])), | ||
| 598 | }; | ||
| 599 | object.reference(id)?; | ||
| 600 | let data_reference = object.reference(data)?; | ||
| 601 | object.set(&[(0x20003415, &data_reference)])?; | ||
| 602 | changed.insert(id, object); | ||
| 603 | hold(active, &mut changed, id, holder, &modified)?; | ||
| 604 | Ok(changed) | ||
| 605 | } | ||
| 606 | |||
| 607 | /// Rewrites a drawing's stroke list: `kept` strokes stay as they are (OneNote erases whole | ||
| 608 | /// strokes rather than editing them), the rest leave the list, `added` ones are created. | ||
| 609 | pub(crate) fn strokes_changes( | ||
| 610 | active: &ActivePage<'_>, | ||
| 611 | container: ExGuid, | ||
| 612 | kept: &[ExGuid], | ||
| 613 | added: &[(ExGuid, &InkStroke)], | ||
| 614 | ) -> Result<Changes, Error> { | ||
| 615 | let data = match active | ||
| 616 | .view | ||
| 617 | .nodes | ||
| 618 | .get(&container) | ||
| 619 | .map(|node| &node.kind) | ||
| 620 | { | ||
| 621 | Some(crate::document::Kind::Ink { | ||
| 622 | data: Some(data), .. | ||
| 623 | }) => *data, | ||
| 624 | _ => return Err(invalid("Stored ink has no stroke data to rewrite")), | ||
| 625 | }; | ||
| 626 | let (modified, filetime) = crate::create::current_timestamps()?; | ||
| 627 | let modified = modified.to_le_bytes(); | ||
| 628 | let raw = &active.live.revision; | ||
| 629 | let mut changed = BTreeMap::new(); | ||
| 630 | let mut data_object = PropertyObject::from_object(&raw.objects[&data])?; | ||
| 631 | let mut references = Vec::new(); | ||
| 632 | for id in kept { | ||
| 633 | references.extend(data_object.reference(*id)?); | ||
| 634 | } | ||
| 635 | references.extend(write_strokes( | ||
| 636 | &mut changed, | ||
| 637 | &mut data_object, | ||
| 638 | added, | ||
| 639 | kept.len(), | ||
| 640 | filetime, | ||
| 641 | )?); | ||
| 642 | data_object.set(&[(0x24003416, &references)])?; | ||
| 643 | changed.insert(data, data_object); | ||
| 644 | let mut object = PropertyObject::from_object(&raw.objects[&container])?; | ||
| 645 | object.set(&[(0x14001d7a, &modified)])?; | ||
| 646 | changed.insert(container, object); | ||
| 647 | Ok(changed) | ||
| 648 | } | ||
| 649 | |||
| 650 | /// An equation as OneNote stores one: the linear text, one run per span with a style | ||
| 651 | /// carrying the span's format, the run-data array naming each run's inline object, and | ||
| 652 | /// the math language marker on the text object. | ||
| 653 | pub(crate) fn equation_changes( | ||
| 654 | active: &ActivePage<'_>, | ||
| 655 | object: ExGuid, | ||
| 656 | text: &Paragraph, | ||
| 657 | ) -> Result<Changes, Error> { | ||
| 658 | if text.text().contains('\u{fffc}') { | ||
| 659 | return Err(invalid("Equations cannot hold embedded objects")); | ||
| 660 | } | ||
| 661 | let encoded: Vec<u8> = text | ||
| 662 | .text() | ||
| 663 | .encode_utf16() | ||
| 664 | .chain([0]) | ||
| 665 | .flat_map(u16::to_le_bytes) | ||
| 666 | .collect(); | ||
| 667 | let mut ends = Vec::new(); | ||
| 668 | let mut styles: Vec<Values> = Vec::new(); | ||
| 669 | let mut sets = Vec::new(); | ||
| 670 | let all_math = text.spans().iter().all(|s| s.format.math == Some(true)); | ||
| 671 | for span in text.spans() { | ||
| 672 | ends.extend(text.utf16_offset(span.end)?.to_le_bytes()); | ||
| 673 | styles.push(style_values(&span.format)); | ||
| 674 | sets.push(match &span.format.math_object { | ||
| 675 | Some(object) => { | ||
| 676 | let mut set = vec![(0x1400344f, object.kind.to_le_bytes().to_vec())]; | ||
| 677 | if let Some(count) = object.arguments { | ||
| 678 | set.push((0x14003450, count.to_le_bytes().to_vec())); | ||
| 679 | } | ||
| 680 | if let Some(columns) = object.columns { | ||
| 681 | set.push((0x0c003451, vec![columns])); | ||
| 682 | } | ||
| 683 | for (id, symbol) in [0x10003453, 0x10003454, 0x10003455] | ||
| 684 | .into_iter() | ||
| 685 | .zip(&object.symbols) | ||
| 686 | { | ||
| 687 | let unit = u16::try_from(u32::from(*symbol)) | ||
| 688 | .map_err(|_| invalid("Math symbols are single UTF-16 units"))?; | ||
| 689 | set.push((id, unit.to_le_bytes().to_vec())); | ||
| 690 | } | ||
| 691 | set | ||
| 692 | } | ||
| 693 | None => Vec::new(), | ||
| 694 | }); | ||
| 695 | } | ||
| 696 | ends.truncate(ends.len() - 4); | ||
| 697 | let has_objects = sets.iter().any(|set| !set.is_empty()); | ||
| 698 | let modified = crate::create::current_timestamps()?.0.to_le_bytes(); | ||
| 699 | let raw = &active.live.revision; | ||
| 700 | let mut changed = BTreeMap::new(); | ||
| 701 | let mut target = PropertyObject::from_object(&raw.objects[&object])?; | ||
| 702 | let mut references = Vec::new(); | ||
| 703 | let mut created: Vec<(Values, ExGuid)> = Vec::new(); | ||
| 704 | for values in &styles { | ||
| 705 | let id = match created.iter().find(|(known, _)| known == values) { | ||
| 706 | Some((_, id)) => *id, | ||
| 707 | None => { | ||
| 708 | let id = ExGuid { | ||
| 709 | guid: crate::write::fresh_guid()?, | ||
| 710 | n: 1, | ||
| 711 | }; | ||
| 712 | let mut style = PropertyObject { | ||
| 713 | jcid: 0x12004d, | ||
| 714 | bytes: crate::create::properties(values)?, | ||
| 715 | global_ids: std::sync::Arc::new(BTreeMap::from([(0, id.guid)])), | ||
| 716 | }; | ||
| 717 | style.reference(id)?; | ||
| 718 | changed.insert(id, style); | ||
| 719 | created.push((values.clone(), id)); | ||
| 720 | id | ||
| 721 | } | ||
| 722 | }; | ||
| 723 | references.extend(target.reference(id)?); | ||
| 724 | } | ||
| 725 | target.remove(&[0x1c003498, 0x40003499])?; | ||
| 726 | target.set(&[ | ||
| 727 | (0x1c001c22, &encoded), | ||
| 728 | (0x1c001e12, &ends), | ||
| 729 | (0x24001e13, &references), | ||
| 730 | (0x14001d7a, &modified), | ||
| 731 | ])?; | ||
| 732 | if has_objects { | ||
| 733 | target.set_sets(0x40003499, 0x44000811, &sets)?; | ||
| 734 | } | ||
| 735 | if all_math { | ||
| 736 | // The flags and language marker OneNote's equation editor leaves on every equation | ||
| 737 | // text object. | ||
| 738 | target.set(&[ | ||
| 739 | (0x10001cfe, &0x7f_u16.to_le_bytes()), | ||
| 740 | (0x14001c3e, &1u32.to_le_bytes()), | ||
| 741 | (0x14001c84, &1u32.to_le_bytes()), | ||
| 742 | ])?; | ||
| 743 | } | ||
| 744 | changed.insert(object, target); | ||
| 745 | Ok(changed) | ||
| 746 | } | ||
crates/onestore/src/op/levels.rs created+143| ... | @@ -0,0 +1,143 @@ | ||
| 1 | //! Outline levels: OneNote stores a container's children at its child level and nests each | ||
| 2 | //! run of deeper paragraphs in an outline group adding the rest | ||
| 3 | //! (`evidence/structural-edits/xml/c6-first-tab.xml`, MS-ONE 2.2.22). | ||
| 4 | |||
| 5 | use crate::{ | ||
| 6 | Error, ExGuid, | ||
| 7 | active::{ActivePage, Changes}, | ||
| 8 | document::{Kind, Revision}, | ||
| 9 | write::PropertyObject, | ||
| 10 | }; | ||
| 11 | use std::collections::BTreeMap; | ||
| 12 | |||
| 13 | fn invalid(message: &'static str) -> Error { | ||
| 14 | Error { offset: 0, message } | ||
| 15 | } | ||
| 16 | |||
| 17 | /// The children of `container` with its outline groups lifted into it, in order. | ||
| 18 | pub(crate) fn flat_children(view: &Revision<'_>, container: ExGuid) -> Vec<ExGuid> { | ||
| 19 | fn flat(view: &Revision<'_>, id: ExGuid, out: &mut Vec<ExGuid>) { | ||
| 20 | for child in &view.nodes[&id].children { | ||
| 21 | match view.nodes[child].kind { | ||
| 22 | Kind::OutlineGroup => flat(view, *child, out), | ||
| 23 | _ => out.push(*child), | ||
| 24 | } | ||
| 25 | } | ||
| 26 | } | ||
| 27 | let mut children = Vec::new(); | ||
| 28 | flat(view, container, &mut children); | ||
| 29 | children | ||
| 30 | } | ||
| 31 | |||
| 32 | /// How much deeper than `container` each of its children lies, groups lifted. | ||
| 33 | pub(crate) fn stored_depths(view: &Revision<'_>, container: ExGuid) -> Vec<(ExGuid, u32)> { | ||
| 34 | fn walk(view: &Revision<'_>, id: ExGuid, base: u32, out: &mut Vec<(ExGuid, u32)>) { | ||
| 35 | let node = &view.nodes[&id]; | ||
| 36 | let depth = base + u32::from(node.child_level.unwrap_or(0)); | ||
| 37 | for child in &node.children { | ||
| 38 | match view.nodes[child].kind { | ||
| 39 | Kind::OutlineGroup => walk(view, *child, depth, out), | ||
| 40 | _ => out.push((*child, depth)), | ||
| 41 | } | ||
| 42 | } | ||
| 43 | } | ||
| 44 | let mut out = Vec::new(); | ||
| 45 | walk(view, container, 0, &mut out); | ||
| 46 | out | ||
| 47 | } | ||
| 48 | |||
| 49 | /// Lifts the children of `container`'s outline groups into it. | ||
| 50 | pub(crate) fn ungroup_changes(active: &ActivePage<'_>, container: ExGuid) -> Result<Changes, Error> { | ||
| 51 | let children = flat_children(&active.view, container); | ||
| 52 | let parents = active.editable_parents(container)?; | ||
| 53 | let modified = crate::create::current_timestamps()?.0.to_le_bytes(); | ||
| 54 | let raw = &active.live.revision; | ||
| 55 | let mut holder = PropertyObject::from_object(&raw.objects[&container])?; | ||
| 56 | let mut references = Vec::new(); | ||
| 57 | for child in children { | ||
| 58 | references.extend(holder.reference(child)?); | ||
| 59 | } | ||
| 60 | holder.set(&[(0x24001c20, &references), (0x14001d7a, &modified)])?; | ||
| 61 | let mut changed = BTreeMap::from([(container, holder)]); | ||
| 62 | crate::formatting::touch_ancestors(raw, parents, container, &modified, &mut changed)?; | ||
| 63 | Ok(changed) | ||
| 64 | } | ||
| 65 | |||
| 66 | /// Children in runs: one child at the container's child level, or a run sharing a deeper | ||
| 67 | /// depth. | ||
| 68 | pub(crate) type Runs = Vec<(u32, Vec<ExGuid>)>; | ||
| 69 | |||
| 70 | /// The child level of a container whose children lie `depths` deeper than it, and its | ||
| 71 | /// children in runs. | ||
| 72 | pub(crate) fn runs(children: &[ExGuid], depths: &[u32], is_cell: bool) -> Result<(u32, Runs), Error> { | ||
| 73 | let level = *depths | ||
| 74 | .iter() | ||
| 75 | .min() | ||
| 76 | .ok_or_else(|| invalid("A container needs a child"))?; | ||
| 77 | if !(1..=31).contains(&level) || depths.iter().any(|depth| *depth > level + 31) { | ||
| 78 | return Err(invalid( | ||
| 79 | "Outline levels lie 1 to 31 deeper than their parent", | ||
| 80 | )); | ||
| 81 | } | ||
| 82 | if is_cell && depths.iter().any(|depth| *depth != level) { | ||
| 83 | return Err(invalid("Table cells hold no outline groups")); | ||
| 84 | } | ||
| 85 | let mut runs: Runs = Vec::new(); | ||
| 86 | for (id, depth) in children.iter().zip(depths) { | ||
| 87 | match runs.last_mut() { | ||
| 88 | Some((last, ids)) if *last == *depth && *depth != level => ids.push(*id), | ||
| 89 | _ => runs.push((*depth, vec![*id])), | ||
| 90 | } | ||
| 91 | } | ||
| 92 | Ok((level, runs)) | ||
| 93 | } | ||
| 94 | |||
| 95 | /// Stores `container`'s children as `runs` below child level `level`, each deeper run in a | ||
| 96 | /// new outline group. | ||
| 97 | pub(crate) fn regroup_changes( | ||
| 98 | active: &ActivePage<'_>, | ||
| 99 | container: ExGuid, | ||
| 100 | level: u32, | ||
| 101 | runs: &[(u32, Vec<ExGuid>)], | ||
| 102 | ) -> Result<Changes, Error> { | ||
| 103 | let parents = active.editable_parents(container)?; | ||
| 104 | let modified = crate::create::current_timestamps()?.0.to_le_bytes(); | ||
| 105 | let raw = &active.live.revision; | ||
| 106 | let mut holder = PropertyObject::from_object(&raw.objects[&container])?; | ||
| 107 | let mut changed = BTreeMap::new(); | ||
| 108 | let mut references = Vec::new(); | ||
| 109 | for (depth, ids) in runs { | ||
| 110 | if *depth == level { | ||
| 111 | references.extend(holder.reference(ids[0])?); | ||
| 112 | continue; | ||
| 113 | } | ||
| 114 | let id = ExGuid { | ||
| 115 | guid: crate::write::fresh_guid()?, | ||
| 116 | n: 1, | ||
| 117 | }; | ||
| 118 | let mut group = PropertyObject { | ||
| 119 | jcid: 0x60019, | ||
| 120 | bytes: crate::create::properties(&[ | ||
| 121 | (0x14001d7a, modified.to_vec()), | ||
| 122 | (0x0c001c03, vec![(depth - level) as u8]), | ||
| 123 | ])?, | ||
| 124 | global_ids: std::sync::Arc::new(BTreeMap::from([(0, id.guid)])), | ||
| 125 | }; | ||
| 126 | let mut members = Vec::new(); | ||
| 127 | for member in ids { | ||
| 128 | members.extend(group.reference(*member)?); | ||
| 129 | } | ||
| 130 | group.set(&[(0x24001c20, &members)])?; | ||
| 131 | group.reference(id)?; | ||
| 132 | changed.insert(id, group); | ||
| 133 | references.extend(holder.reference(id)?); | ||
| 134 | } | ||
| 135 | holder.set(&[ | ||
| 136 | (0x24001c20, &references), | ||
| 137 | (0x0c001c03, &[level as u8]), | ||
| 138 | (0x14001d7a, &modified), | ||
| 139 | ])?; | ||
| 140 | changed.insert(container, holder); | ||
| 141 | crate::formatting::touch_ancestors(raw, parents, container, &modified, &mut changed)?; | ||
| 142 | Ok(changed) | ||
| 143 | } | ||
crates/onestore/src/op/lower.rs created+1778| ... | @@ -0,0 +1,1778 @@ | ||
| 1 | //! The ops that turn one page model into another, computed from the models alone: each op | ||
| 2 | //! is interpreted on a prediction of the page (`model::apply`) so later ones see what earlier | ||
| 3 | //! ones leave, as the typed writers would store it. | ||
| 4 | |||
| 5 | use super::{PageOp, TableEdit, TextProperty, Values, model}; | ||
| 6 | use crate::{ | ||
| 7 | Error, ExGuid, TextAttribute, | ||
| 8 | document::{Format, Kind, Tag}, | ||
| 9 | page::{ | ||
| 10 | Definition, Outline, Page, PageObject, PageParagraph, Paragraph, ParagraphContent, Table, | ||
| 11 | TableCell, TextObject, text::new_id, | ||
| 12 | }, | ||
| 13 | }; | ||
| 14 | use std::{ | ||
| 15 | collections::{BTreeMap, BTreeSet}, | ||
| 16 | ops::Range, | ||
| 17 | }; | ||
| 18 | |||
| 19 | fn invalid(message: &'static str) -> Error { | ||
| 20 | Error { offset: 0, message } | ||
| 21 | } | ||
| 22 | |||
| 23 | /// The ops that turn `before`, a page as stored, into `after`. | ||
| 24 | pub fn lower_page(before: &Page, after: &Page) -> Result<Vec<PageOp>, Error> { | ||
| 25 | let old = View::new(before, false)?; | ||
| 26 | let new = View::new(after, false)?; | ||
| 27 | Lowering::new(before.clone(), None).run(before, after, &old, &new) | ||
| 28 | } | ||
| 29 | |||
| 30 | /// The ops that replace `before`, consecutive paragraphs of outline or table cell | ||
| 31 | /// `container` with their parents listed first or outside the range, by `after`. `next` | ||
| 32 | /// is the paragraph following the range; `definitions` holds the lists, tags and styles | ||
| 33 | /// the paragraphs reference. Only the paragraphs given are read. | ||
| 34 | pub fn lower( | ||
| 35 | container: ExGuid, | ||
| 36 | before: &[PageParagraph], | ||
| 37 | after: &[PageParagraph], | ||
| 38 | next: Option<&PageParagraph>, | ||
| 39 | definitions: &BTreeMap<ExGuid, Definition>, | ||
| 40 | ) -> Result<Vec<PageOp>, Error> { | ||
| 41 | let page = |paragraphs: &[PageParagraph]| Page { | ||
| 42 | title: String::new(), | ||
| 43 | identity: None, | ||
| 44 | created: None, | ||
| 45 | margin_origin: [0.0; 2], | ||
| 46 | objects: vec![PageObject::Outline(Outline { | ||
| 47 | id: container, | ||
| 48 | title: false, | ||
| 49 | min_width: None, | ||
| 50 | layout: Default::default(), | ||
| 51 | indents: Vec::new(), | ||
| 52 | paragraphs: paragraphs.to_vec(), | ||
| 53 | unsupported: Vec::new(), | ||
| 54 | })], | ||
| 55 | definitions: definitions.clone(), | ||
| 56 | }; | ||
| 57 | let (before, after) = (page(before), page(after)); | ||
| 58 | let old = View::new(&before, true)?; | ||
| 59 | let new = View::new(&after, true)?; | ||
| 60 | let tail = next.map(|next| (next.parent.unwrap_or(container), next.id)); | ||
| 61 | Lowering::new(before.clone(), tail).run(&before, &after, &old, &new) | ||
| 62 | } | ||
| 63 | |||
| 64 | /// Direct children of every container, in model order, plus lookups by identity. | ||
| 65 | pub(crate) struct View<'a> { | ||
| 66 | pub page: &'a Page, | ||
| 67 | pub outlines: BTreeMap<ExGuid, &'a Outline>, | ||
| 68 | /// Outlines owned by a title object rather than the page. | ||
| 69 | pub title_outlines: BTreeSet<ExGuid>, | ||
| 70 | pub paragraphs: BTreeMap<ExGuid, &'a PageParagraph>, | ||
| 71 | pub children: BTreeMap<ExGuid, Vec<ExGuid>>, | ||
| 72 | pub container: BTreeMap<ExGuid, ExGuid>, | ||
| 73 | pub page_children: Vec<ExGuid>, | ||
| 74 | /// Containers a partial model names as parents without holding them, in order. | ||
| 75 | pub external: Vec<ExGuid>, | ||
| 76 | partial: bool, | ||
| 77 | } | ||
| 78 | |||
| 79 | impl<'a> View<'a> { | ||
| 80 | /// `partial` accepts parents outside the model, as a range of paragraphs has. | ||
| 81 | pub(crate) fn new(page: &'a Page, partial: bool) -> Result<Self, Error> { | ||
| 82 | let mut view = Self { | ||
| 83 | page, | ||
| 84 | outlines: BTreeMap::new(), | ||
| 85 | title_outlines: BTreeSet::new(), | ||
| 86 | paragraphs: BTreeMap::new(), | ||
| 87 | children: BTreeMap::new(), | ||
| 88 | container: BTreeMap::new(), | ||
| 89 | page_children: Vec::new(), | ||
| 90 | external: Vec::new(), | ||
| 91 | partial, | ||
| 92 | }; | ||
| 93 | for object in &page.objects { | ||
| 94 | view.page_children.push(object.id()); | ||
| 95 | match object { | ||
| 96 | PageObject::Outline(outline) => view.outline(outline)?, | ||
| 97 | PageObject::Title(title) => { | ||
| 98 | for outline in &title.outlines { | ||
| 99 | view.title_outlines.insert(outline.id); | ||
| 100 | view.outline(outline)?; | ||
| 101 | } | ||
| 102 | } | ||
| 103 | PageObject::Image(_) | PageObject::Ink(_) | PageObject::Unsupported(_) => {} | ||
| 104 | } | ||
| 105 | } | ||
| 106 | Ok(view) | ||
| 107 | } | ||
| 108 | |||
| 109 | fn outline(&mut self, outline: &'a Outline) -> Result<(), Error> { | ||
| 110 | if self.outlines.insert(outline.id, outline).is_some() { | ||
| 111 | return Err(invalid("The page model repeats an outline identity")); | ||
| 112 | } | ||
| 113 | self.children.entry(outline.id).or_default(); | ||
| 114 | self.paragraphs_of(outline.id, &outline.paragraphs) | ||
| 115 | } | ||
| 116 | |||
| 117 | fn paragraphs_of(&mut self, root: ExGuid, list: &'a [PageParagraph]) -> Result<(), Error> { | ||
| 118 | let listed: BTreeSet<ExGuid> = if self.partial { | ||
| 119 | list.iter().map(|p| p.id).collect() | ||
| 120 | } else { | ||
| 121 | BTreeSet::new() | ||
| 122 | }; | ||
| 123 | for paragraph in list { | ||
| 124 | let container = paragraph.parent.unwrap_or(root); | ||
| 125 | if let Some(parent) = paragraph.parent | ||
| 126 | && !self.paragraphs.contains_key(&parent) | ||
| 127 | { | ||
| 128 | if !self.partial || listed.contains(&parent) { | ||
| 129 | return Err(invalid( | ||
| 130 | "A paragraph's parent must precede it in its container", | ||
| 131 | )); | ||
| 132 | } | ||
| 133 | if !self.external.contains(&parent) { | ||
| 134 | self.external.push(parent); | ||
| 135 | } | ||
| 136 | } | ||
| 137 | if self.paragraphs.insert(paragraph.id, paragraph).is_some() { | ||
| 138 | return Err(invalid("The page model repeats a paragraph identity")); | ||
| 139 | } | ||
| 140 | self.children | ||
| 141 | .entry(container) | ||
| 142 | .or_default() | ||
| 143 | .push(paragraph.id); | ||
| 144 | self.children.entry(paragraph.id).or_default(); | ||
| 145 | self.container.insert(paragraph.id, container); | ||
| 146 | if let ParagraphContent::Table(table) = &paragraph.content { | ||
| 147 | for row in &table.rows { | ||
| 148 | for cell in &row.cells { | ||
| 149 | if self.children.contains_key(&cell.id) { | ||
| 150 | return Err(invalid("The page model repeats a cell identity")); | ||
| 151 | } | ||
| 152 | self.children.entry(cell.id).or_default(); | ||
| 153 | self.paragraphs_of(cell.id, &cell.paragraphs)?; | ||
| 154 | } | ||
| 155 | } | ||
| 156 | } | ||
| 157 | } | ||
| 158 | Ok(()) | ||
| 159 | } | ||
| 160 | |||
| 161 | pub(crate) fn text(&self, paragraph: ExGuid) -> Option<&'a TextObject> { | ||
| 162 | self.paragraphs.get(&paragraph).and_then(|p| p.text()) | ||
| 163 | } | ||
| 164 | |||
| 165 | /// How much deeper than `container` each of its children lies: their outline levels | ||
| 166 | /// less the paragraph's, or less nothing in an outline or cell. | ||
| 167 | pub(crate) fn depths(&self, container: ExGuid, children: &[ExGuid]) -> Option<Vec<u32>> { | ||
| 168 | let base = self.paragraphs.get(&container).map_or(0, |p| p.level); | ||
| 169 | children | ||
| 170 | .iter() | ||
| 171 | .map(|id| self.paragraphs.get(id)?.level.checked_sub(base)) | ||
| 172 | .collect() | ||
| 173 | } | ||
| 174 | |||
| 175 | /// Each outline's paragraphs in document order, a table's cells following its paragraph. | ||
| 176 | pub(crate) fn documents(&self) -> impl Iterator<Item = Vec<&'a PageParagraph>> + '_ { | ||
| 177 | fn walk<'a>(list: &'a [PageParagraph], out: &mut Vec<&'a PageParagraph>) { | ||
| 178 | for paragraph in list { | ||
| 179 | out.push(paragraph); | ||
| 180 | if let ParagraphContent::Table(table) = &paragraph.content { | ||
| 181 | for cell in table.rows.iter().flat_map(|row| &row.cells) { | ||
| 182 | walk(&cell.paragraphs, out); | ||
| 183 | } | ||
| 184 | } | ||
| 185 | } | ||
| 186 | } | ||
| 187 | self.outlines.values().map(|outline| { | ||
| 188 | let mut out = Vec::new(); | ||
| 189 | walk(&outline.paragraphs, &mut out); | ||
| 190 | out | ||
| 191 | }) | ||
| 192 | } | ||
| 193 | |||
| 194 | /// Every container in document order: outlines, external parents, paragraphs and cells. | ||
| 195 | fn containers(&self) -> Vec<ExGuid> { | ||
| 196 | let mut containers = Vec::new(); | ||
| 197 | for object in &self.page.objects { | ||
| 198 | let outlines: Vec<&Outline> = match object { | ||
| 199 | PageObject::Outline(outline) => vec![outline], | ||
| 200 | PageObject::Title(title) => title.outlines.iter().collect(), | ||
| 201 | PageObject::Image(_) | PageObject::Ink(_) | PageObject::Unsupported(_) => { | ||
| 202 | Vec::new() | ||
| 203 | } | ||
| 204 | }; | ||
| 205 | for outline in outlines { | ||
| 206 | containers.push(outline.id); | ||
| 207 | containers.extend(&self.external); | ||
| 208 | collect_containers(&outline.paragraphs, &mut containers); | ||
| 209 | } | ||
| 210 | } | ||
| 211 | containers | ||
| 212 | } | ||
| 213 | } | ||
| 214 | |||
| 215 | pub(crate) fn collect_containers(list: &[PageParagraph], out: &mut Vec<ExGuid>) { | ||
| 216 | for paragraph in list { | ||
| 217 | out.push(paragraph.id); | ||
| 218 | if let ParagraphContent::Table(table) = &paragraph.content { | ||
| 219 | for row in &table.rows { | ||
| 220 | for cell in &row.cells { | ||
| 221 | out.push(cell.id); | ||
| 222 | collect_containers(&cell.paragraphs, out); | ||
| 223 | } | ||
| 224 | } | ||
| 225 | } | ||
| 226 | } | ||
| 227 | } | ||
| 228 | |||
| 229 | /// What the writers keep as stored, checked before any op: titles, unsupported objects, | ||
| 230 | /// definitions other than lists and tags, outline roles, paragraph styles and formats, | ||
| 231 | /// recording links, fields and content types. | ||
| 232 | pub(crate) fn validate( | ||
| 233 | before: &Page, | ||
| 234 | after: &Page, | ||
| 235 | old: &View<'_>, | ||
| 236 | new: &View<'_>, | ||
| 237 | ) -> Result<(), Error> { | ||
| 238 | if after.created != before.created || after.margin_origin != before.margin_origin { | ||
| 239 | return Err(invalid("Page creation time and margins cannot be edited")); | ||
| 240 | } | ||
| 241 | for (id, definition) in &after.definitions { | ||
| 242 | if before | ||
| 243 | .definitions | ||
| 244 | .get(id) | ||
| 245 | .is_some_and(|stored| stored != definition) | ||
| 246 | && !matches!( | ||
| 247 | definition.kind, | ||
| 248 | Kind::List { .. } | Kind::TagDefinition { .. } | ||
| 249 | ) | ||
| 250 | { | ||
| 251 | return Err(invalid("Style definitions cannot be edited")); | ||
| 252 | } | ||
| 253 | } | ||
| 254 | for paragraph in new.paragraphs.values() { | ||
| 255 | if let ParagraphContent::Table(table) = &paragraph.content { | ||
| 256 | super::table::validate_table(table)?; | ||
| 257 | } | ||
| 258 | } | ||
| 259 | let fixed = |page: &Page| -> Vec<String> { | ||
| 260 | let mut fixed: Vec<String> = page | ||
| 261 | .objects | ||
| 262 | .iter() | ||
| 263 | .filter_map(|object| match object { | ||
| 264 | PageObject::Unsupported(unsupported) => Some(format!("{unsupported:?}")), | ||
| 265 | PageObject::Title(title) => Some(format!( | ||
| 266 | "{:?} {:?} {:?} {:?}", | ||
| 267 | title.id, | ||
| 268 | title.date, | ||
| 269 | title.layout, | ||
| 270 | title.outlines.iter().map(|o| o.id).collect::<Vec<_>>() | ||
| 271 | )), | ||
| 272 | PageObject::Outline(_) | PageObject::Image(_) | PageObject::Ink(_) => None, | ||
| 273 | }) | ||
| 274 | .collect(); | ||
| 275 | fixed.sort(); | ||
| 276 | fixed | ||
| 277 | }; | ||
| 278 | if fixed(before) != fixed(after) { | ||
| 279 | return Err(invalid( | ||
| 280 | "Titles and unsupported objects cannot be edited through the page model", | ||
| 281 | )); | ||
| 282 | } | ||
| 283 | for (id, outline) in &new.outlines { | ||
| 284 | if outline.paragraphs.is_empty() { | ||
| 285 | return Err(invalid( | ||
| 286 | "An outline needs a paragraph; remove the outline instead", | ||
| 287 | )); | ||
| 288 | } | ||
| 289 | let Some(previous) = old.outlines.get(id) else { | ||
| 290 | continue; | ||
| 291 | }; | ||
| 292 | let same = outline.title == previous.title | ||
| 293 | && outline.min_width == previous.min_width | ||
| 294 | && outline.indents == previous.indents | ||
| 295 | && outline.unsupported == previous.unsupported | ||
| 296 | && (!new.title_outlines.contains(id) || outline.layout == previous.layout); | ||
| 297 | if !same { | ||
| 298 | return Err(invalid( | ||
| 299 | "Outline roles, indentation tables and title geometry cannot be edited", | ||
| 300 | )); | ||
| 301 | } | ||
| 302 | } | ||
| 303 | let owners: BTreeMap<ExGuid, &PageParagraph> = old | ||
| 304 | .paragraphs | ||
| 305 | .values() | ||
| 306 | .filter_map(|owner| Some((owner.text()?.id, *owner))) | ||
| 307 | .collect(); | ||
| 308 | for (id, paragraph) in &new.paragraphs { | ||
| 309 | let Some(previous) = old.paragraphs.get(id) else { | ||
| 310 | continue; | ||
| 311 | }; | ||
| 312 | // A join moves a text object, with its style and recording link, into an emptied | ||
| 313 | // paragraph; one new to the page starts as a copy without the link. | ||
| 314 | let owner = paragraph.text().map(|text| owners.get(&text.id)); | ||
| 315 | let (style, media) = match owner { | ||
| 316 | Some(Some(owner)) => (owner.style, owner.media.clone()), | ||
| 317 | Some(None) => (paragraph.style, Default::default()), | ||
| 318 | None => (previous.style, previous.media.clone()), | ||
| 319 | }; | ||
| 320 | if paragraph.media != media { | ||
| 321 | return Err(invalid("Recording annotations cannot be edited")); | ||
| 322 | } | ||
| 323 | if paragraph.style != style || paragraph.format != previous.format { | ||
| 324 | return Err(invalid( | ||
| 325 | "Paragraph styles and paragraph formatting cannot be edited", | ||
| 326 | )); | ||
| 327 | } | ||
| 328 | match (&paragraph.content, &previous.content) { | ||
| 329 | (ParagraphContent::Text(text), ParagraphContent::Text(previous)) => { | ||
| 330 | if text.date_field != previous.date_field { | ||
| 331 | return Err(invalid("Text fields cannot be edited")); | ||
| 332 | } | ||
| 333 | } | ||
| 334 | (ParagraphContent::Table(table), ParagraphContent::Table(previous)) => { | ||
| 335 | if table.id != previous.id | ||
| 336 | || table.layout != previous.layout | ||
| 337 | || table.tags != previous.tags | ||
| 338 | { | ||
| 339 | return Err(invalid("Table identity, layout and tags cannot be edited")); | ||
| 340 | } | ||
| 341 | let cells: BTreeMap<ExGuid, &TableCell> = previous | ||
| 342 | .rows | ||
| 343 | .iter() | ||
| 344 | .flat_map(|row| &row.cells) | ||
| 345 | .map(|cell| (cell.id, cell)) | ||
| 346 | .collect(); | ||
| 347 | let unchanged_cells = table.rows.iter().flat_map(|row| &row.cells).all(|cell| { | ||
| 348 | cells.get(&cell.id).is_none_or(|before| { | ||
| 349 | cell.layout == before.layout && cell.unsupported == before.unsupported | ||
| 350 | }) | ||
| 351 | }); | ||
| 352 | if !unchanged_cells { | ||
| 353 | return Err(invalid("Cell layout cannot be edited")); | ||
| 354 | } | ||
| 355 | } | ||
| 356 | (ParagraphContent::Unsupported(a), ParagraphContent::Unsupported(b)) if a == b => {} | ||
| 357 | (ParagraphContent::Ink(a), ParagraphContent::Ink(b)) => { | ||
| 358 | if a.id != b.id { | ||
| 359 | return Err(invalid("Ink identity cannot change")); | ||
| 360 | } | ||
| 361 | } | ||
| 362 | (ParagraphContent::Image(a), ParagraphContent::Image(b)) => { | ||
| 363 | super::content::picture_fixed_fields(a, b)?; | ||
| 364 | if (a.layout.x, a.layout.y) != (b.layout.x, b.layout.y) { | ||
| 365 | return Err(invalid("A paragraph picture has no position of its own")); | ||
| 366 | } | ||
| 367 | } | ||
| 368 | (ParagraphContent::Attachment(a), ParagraphContent::Attachment(b)) => { | ||
| 369 | if a.id != b.id { | ||
| 370 | return Err(invalid("Attachment identity cannot change")); | ||
| 371 | } | ||
| 372 | } | ||
| 373 | _ => return Err(invalid("Paragraph content type cannot change")), | ||
| 374 | } | ||
| 375 | } | ||
| 376 | Ok(()) | ||
| 377 | } | ||
| 378 | |||
| 379 | /// Identities that keep their stored position: a longest increasing run of survivors, | ||
| 380 | /// always including immovable ones. `after` may contain identities absent from `before`. | ||
| 381 | pub(crate) fn kept_set( | ||
| 382 | before: &[ExGuid], | ||
| 383 | after: &[ExGuid], | ||
| 384 | movable: impl Fn(ExGuid) -> bool, | ||
| 385 | ) -> Result<BTreeSet<ExGuid>, Error> { | ||
| 386 | let position: BTreeMap<ExGuid, usize> = | ||
| 387 | after.iter().enumerate().map(|(i, id)| (*id, i)).collect(); | ||
| 388 | let sequence: Vec<(ExGuid, usize)> = before | ||
| 389 | .iter() | ||
| 390 | .filter_map(|id| position.get(id).map(|at| (*id, *at))) | ||
| 391 | .collect(); | ||
| 392 | let fixed: Vec<usize> = sequence | ||
| 393 | .iter() | ||
| 394 | .filter(|(id, _)| !movable(*id)) | ||
| 395 | .map(|(_, at)| *at) | ||
| 396 | .collect(); | ||
| 397 | if fixed.windows(2).any(|w| w[0] > w[1]) { | ||
| 398 | return Err(invalid( | ||
| 399 | "Images and unsupported objects cannot be reordered", | ||
| 400 | )); | ||
| 401 | } | ||
| 402 | if sequence.windows(2).all(|pair| pair[0].1 < pair[1].1) { | ||
| 403 | return Ok(sequence.into_iter().map(|(id, _)| id).collect()); | ||
| 404 | } | ||
| 405 | // Longest increasing subsequence over `after` positions, forced through immovable items. | ||
| 406 | let n = sequence.len(); | ||
| 407 | let mut best = vec![1usize; n]; | ||
| 408 | let mut previous = vec![usize::MAX; n]; | ||
| 409 | for i in 0..n { | ||
| 410 | let (id, at) = sequence[i]; | ||
| 411 | let mandatory_before = sequence[..i] | ||
| 412 | .iter() | ||
| 413 | .filter(|(other, _)| !movable(*other)) | ||
| 414 | .map(|(_, at)| *at) | ||
| 415 | .max(); | ||
| 416 | if movable(id) && mandatory_before.is_some_and(|m| m > at) { | ||
| 417 | best[i] = 0; | ||
| 418 | continue; | ||
| 419 | } | ||
| 420 | let mandatory_after = sequence[i + 1..] | ||
| 421 | .iter() | ||
| 422 | .filter(|(other, _)| !movable(*other)) | ||
| 423 | .map(|(_, at)| *at) | ||
| 424 | .min(); | ||
| 425 | if movable(id) && mandatory_after.is_some_and(|m| m < at) { | ||
| 426 | best[i] = 0; | ||
| 427 | continue; | ||
| 428 | } | ||
| 429 | for j in 0..i { | ||
| 430 | if best[j] > 0 && sequence[j].1 < at && best[j] + 1 > best[i] { | ||
| 431 | best[i] = best[j] + 1; | ||
| 432 | previous[i] = j; | ||
| 433 | } | ||
| 434 | } | ||
| 435 | } | ||
| 436 | let mut kept = BTreeSet::new(); | ||
| 437 | if let Some((mut i, _)) = best | ||
| 438 | .iter() | ||
| 439 | .enumerate() | ||
| 440 | .max_by_key(|(i, b)| (**b, usize::MAX - i)) | ||
| 441 | && best[i] > 0 | ||
| 442 | { | ||
| 443 | loop { | ||
| 444 | kept.insert(sequence[i].0); | ||
| 445 | if previous[i] == usize::MAX { | ||
| 446 | break; | ||
| 447 | } | ||
| 448 | i = previous[i]; | ||
| 449 | } | ||
| 450 | } | ||
| 451 | for (id, _) in &sequence { | ||
| 452 | if !movable(*id) && !kept.contains(id) { | ||
| 453 | return Err(invalid( | ||
| 454 | "Images and unsupported objects cannot be reordered", | ||
| 455 | )); | ||
| 456 | } | ||
| 457 | } | ||
| 458 | Ok(kept) | ||
| 459 | } | ||
| 460 | |||
| 461 | /// The format of the span containing the UTF-16 position `at` (the following span at a | ||
| 462 | /// boundary, the last at the end), which text inserted there takes. | ||
| 463 | pub(crate) fn format_in(paragraph: &Paragraph, at: u32) -> Result<&Format, Error> { | ||
| 464 | let byte = paragraph.byte_offset(at)?; | ||
| 465 | let spans = paragraph.spans(); | ||
| 466 | let index = spans | ||
| 467 | .partition_point(|span| span.end <= byte) | ||
| 468 | .min(spans.len() - 1); | ||
| 469 | Ok(&spans[index].format) | ||
| 470 | } | ||
| 471 | |||
| 472 | /// The smallest UTF-16 range whose replacement turns `before` into `after`. | ||
| 473 | pub(crate) fn text_edit(before: &str, after: &str) -> Result<(Range<u32>, String), Error> { | ||
| 474 | let prefix = before | ||
| 475 | .char_indices() | ||
| 476 | .zip(after.chars()) | ||
| 477 | .take_while(|((_, a), b)| a == b) | ||
| 478 | .map(|((i, a), _)| i + a.len_utf8()) | ||
| 479 | .last() | ||
| 480 | .unwrap_or(0); | ||
| 481 | let suffix = before[prefix..] | ||
| 482 | .chars() | ||
| 483 | .rev() | ||
| 484 | .zip(after[prefix..].chars().rev()) | ||
| 485 | .take_while(|(a, b)| a == b) | ||
| 486 | .map(|(a, _)| a.len_utf8()) | ||
| 487 | .sum::<usize>(); | ||
| 488 | let units = |s: &str| -> Result<u32, Error> { | ||
| 489 | u32::try_from(s.encode_utf16().count()) | ||
| 490 | .map_err(|_| invalid("Text exceeds UTF-16 offset range")) | ||
| 491 | }; | ||
| 492 | let start = units(&before[..prefix])?; | ||
| 493 | let end = start + units(&before[prefix..before.len() - suffix])?; | ||
| 494 | Ok((start..end, after[prefix..after.len() - suffix].to_owned())) | ||
| 495 | } | ||
| 496 | |||
| 497 | /// A character-format change over a range: attributes to set and properties to clear. | ||
| 498 | pub(crate) type FormatEdit = (Range<u32>, Vec<TextAttribute>, Vec<TextProperty>); | ||
| 499 | |||
| 500 | /// The format edits turning `stored`'s spans into `target`'s, which has the same text. A | ||
| 501 | /// `fresh` text object was created by this edit, so an unset language keeps the writer's. | ||
| 502 | pub(crate) fn format_edits( | ||
| 503 | stored: &Paragraph, | ||
| 504 | target: &Paragraph, | ||
| 505 | fresh: bool, | ||
| 506 | ) -> Result<Vec<FormatEdit>, Error> { | ||
| 507 | let mut boundaries = BTreeSet::new(); | ||
| 508 | for paragraph in [stored, target] { | ||
| 509 | for span in paragraph.spans() { | ||
| 510 | boundaries.insert(paragraph.utf16_offset(span.end)?); | ||
| 511 | } | ||
| 512 | } | ||
| 513 | boundaries.insert(0); | ||
| 514 | let boundaries: Vec<u32> = boundaries.into_iter().collect(); | ||
| 515 | let mut pending: Option<FormatEdit> = None; | ||
| 516 | let mut edits = Vec::new(); | ||
| 517 | for window in boundaries.windows(2) { | ||
| 518 | let (start, end) = (window[0], window[1]); | ||
| 519 | if start == end { | ||
| 520 | continue; | ||
| 521 | } | ||
| 522 | let (set, clear) = attributes(format_in(stored, start)?, format_in(target, start)?, fresh)?; | ||
| 523 | match &mut pending { | ||
| 524 | Some((range, previous_set, previous_clear)) | ||
| 525 | if *previous_set == set && *previous_clear == clear && range.end == start => | ||
| 526 | { | ||
| 527 | range.end = end; | ||
| 528 | } | ||
| 529 | _ => { | ||
| 530 | if let Some(edit) = pending.take() { | ||
| 531 | edits.push(edit); | ||
| 532 | } | ||
| 533 | pending = Some((start..end, set, clear)); | ||
| 534 | } | ||
| 535 | } | ||
| 536 | } | ||
| 537 | edits.extend(pending); | ||
| 538 | if target.text().is_empty() { | ||
| 539 | let (set, clear) = attributes(format_in(stored, 0)?, format_in(target, 0)?, fresh)?; | ||
| 540 | edits.push((0..0, set, clear)); | ||
| 541 | } | ||
| 542 | edits.retain(|(_, set, clear)| !set.is_empty() || !clear.is_empty()); | ||
| 543 | Ok(edits) | ||
| 544 | } | ||
| 545 | |||
| 546 | /// The change turning `current` into `target`; unsupported differences are errors. A value the | ||
| 547 | /// target leaves unset is cleared from the run, as the model's formats are complete. | ||
| 548 | fn attributes( | ||
| 549 | current: &Format, | ||
| 550 | target: &Format, | ||
| 551 | fresh: bool, | ||
| 552 | ) -> Result<(Vec<TextAttribute>, Vec<TextProperty>), Error> { | ||
| 553 | let mut out = Vec::new(); | ||
| 554 | let mut cleared = Vec::new(); | ||
| 555 | macro_rules! boolean { | ||
| 556 | ($field:ident, $variant:ident) => { | ||
| 557 | match target.$field { | ||
| 558 | Some(value) if current.$field != Some(value) => { | ||
| 559 | out.push(TextAttribute::$variant(value)) | ||
| 560 | } | ||
| 561 | None if current.$field.is_some() => cleared.push(TextProperty::$variant), | ||
| 562 | _ => {} | ||
| 563 | } | ||
| 564 | }; | ||
| 565 | } | ||
| 566 | boolean!(bold, Bold); | ||
| 567 | boolean!(italic, Italic); | ||
| 568 | boolean!(underline, Underline); | ||
| 569 | boolean!(strike, Strike); | ||
| 570 | boolean!(superscript, Superscript); | ||
| 571 | boolean!(subscript, Subscript); | ||
| 572 | boolean!(hidden, Hidden); | ||
| 573 | boolean!(hyperlink, Hyperlink); | ||
| 574 | boolean!(hyperlink_label, HyperlinkLabel); | ||
| 575 | boolean!(math, Math); | ||
| 576 | macro_rules! value { | ||
| 577 | ($field:ident, $property:ident, $attribute:expr) => { | ||
| 578 | if current.$field != target.$field { | ||
| 579 | match &target.$field { | ||
| 580 | Some(value) => out.push($attribute(value)), | ||
| 581 | None => cleared.push(TextProperty::$property), | ||
| 582 | } | ||
| 583 | } | ||
| 584 | }; | ||
| 585 | } | ||
| 586 | let color = | ||
| 587 | |value: u32| (value != 0xff000000).then(|| value.to_le_bytes()[..3].try_into().unwrap()); | ||
| 588 | value!(font, Font, |font: &String| TextAttribute::Font(font.clone())); | ||
| 589 | value!(font_size, FontSize, |size: &f32| TextAttribute::FontSize( | ||
| 590 | *size | ||
| 591 | )); | ||
| 592 | value!(color, Color, |value: &u32| TextAttribute::Color(color( | ||
| 593 | *value | ||
| 594 | ))); | ||
| 595 | value!(highlight, Highlight, |value: &u32| { | ||
| 596 | TextAttribute::Highlight(color(*value)) | ||
| 597 | }); | ||
| 598 | if current.language != target.language { | ||
| 599 | match target.language { | ||
| 600 | Some(language) => out.push(TextAttribute::Language(language)), | ||
| 601 | None if fresh => {} | ||
| 602 | None => { | ||
| 603 | return Err(invalid("Inherited character formatting cannot be restored")); | ||
| 604 | } | ||
| 605 | } | ||
| 606 | } | ||
| 607 | // An absent value and its stored default are the same formatting. | ||
| 608 | let flag = |a: Option<bool>, b: Option<bool>| a.unwrap_or(false) == b.unwrap_or(false); | ||
| 609 | let points = |a: Option<f32>, b: Option<f32>| a.unwrap_or(0.0) == b.unwrap_or(0.0); | ||
| 610 | let same_rest = flag(current.embedded_object, target.embedded_object) | ||
| 611 | && current.alignment.unwrap_or(0) == target.alignment.unwrap_or(0) | ||
| 612 | && flag(current.rtl, target.rtl) | ||
| 613 | && points(current.space_before, target.space_before) | ||
| 614 | && points(current.space_after, target.space_after) | ||
| 615 | && points(current.line_spacing, target.line_spacing) | ||
| 616 | && points(current.list_spacing, target.list_spacing); | ||
| 617 | if !same_rest { | ||
| 618 | return Err(invalid( | ||
| 619 | "Fields and paragraph spacing cannot be edited through the page model", | ||
| 620 | )); | ||
| 621 | } | ||
| 622 | Ok((out, cleared)) | ||
| 623 | } | ||
| 624 | |||
| 625 | /// Paragraph formatting a text object stores: alignment, direction, spacing and language. | ||
| 626 | #[derive(Clone, Copy, Debug, Default, PartialEq)] | ||
| 627 | pub(crate) struct ParagraphFields { | ||
| 628 | pub alignment: Option<u8>, | ||
| 629 | pub rtl: Option<bool>, | ||
| 630 | pub space_before: Option<f32>, | ||
| 631 | pub space_after: Option<f32>, | ||
| 632 | pub line_spacing: Option<f32>, | ||
| 633 | pub language: Option<u32>, | ||
| 634 | } | ||
| 635 | |||
| 636 | impl ParagraphFields { | ||
| 637 | pub(crate) fn values(&self) -> Result<Values, Error> { | ||
| 638 | super::properties::paragraph_values( | ||
| 639 | self.alignment, | ||
| 640 | self.rtl, | ||
| 641 | self.space_before, | ||
| 642 | self.space_after, | ||
| 643 | self.line_spacing, | ||
| 644 | self.language, | ||
| 645 | ) | ||
| 646 | } | ||
| 647 | } | ||
| 648 | |||
| 649 | /// The paragraph formatting `target` sets throughout that `stored` lacks somewhere. | ||
| 650 | pub(crate) fn paragraph_fields( | ||
| 651 | stored: &Paragraph, | ||
| 652 | target: &Paragraph, | ||
| 653 | ) -> Result<ParagraphFields, Error> { | ||
| 654 | let first = format_in(target, 0)?; | ||
| 655 | macro_rules! field { | ||
| 656 | ($field:ident) => {{ | ||
| 657 | let value = first.$field.unwrap_or_default(); | ||
| 658 | (target | ||
| 659 | .spans() | ||
| 660 | .iter() | ||
| 661 | .all(|span| span.format.$field.unwrap_or_default() == value) | ||
| 662 | && stored | ||
| 663 | .spans() | ||
| 664 | .iter() | ||
| 665 | .any(|span| span.format.$field.unwrap_or_default() != value)) | ||
| 666 | .then_some(value) | ||
| 667 | }}; | ||
| 668 | } | ||
| 669 | Ok(ParagraphFields { | ||
| 670 | alignment: field!(alignment), | ||
| 671 | rtl: field!(rtl), | ||
| 672 | space_before: field!(space_before), | ||
| 673 | space_after: field!(space_after), | ||
| 674 | line_spacing: field!(line_spacing), | ||
| 675 | language: field!(language), | ||
| 676 | }) | ||
| 677 | } | ||
| 678 | |||
| 679 | /// `paragraph_fields` encoded, as the page writer stores them. | ||
| 680 | pub(crate) fn paragraph_change(stored: &Paragraph, target: &Paragraph) -> Result<Values, Error> { | ||
| 681 | paragraph_fields(stored, target)?.values() | ||
| 682 | } | ||
| 683 | |||
| 684 | /// Whether two tag lists store the same tags. | ||
| 685 | pub(crate) fn same_tags(a: &[Tag], b: &[Tag]) -> bool { | ||
| 686 | let key = |t: &Tag| { | ||
| 687 | ( | ||
| 688 | t.definition, | ||
| 689 | t.action_type, | ||
| 690 | t.status, | ||
| 691 | t.created, | ||
| 692 | t.completed, | ||
| 693 | t.start, | ||
| 694 | t.due, | ||
| 695 | t.task_id, | ||
| 696 | ) | ||
| 697 | }; | ||
| 698 | a.len() == b.len() && a.iter().zip(b).all(|(x, y)| key(x) == key(y)) | ||
| 699 | } | ||
| 700 | |||
| 701 | /// Whether the writers can split or join this text: ordinary text without generated fields, | ||
| 702 | /// equations or embedded objects. | ||
| 703 | fn ordinary(text: &TextObject) -> bool { | ||
| 704 | text.date_field.is_none() | ||
| 705 | && !text.text.text().contains('\u{fffc}') | ||
| 706 | && text.text.spans().iter().all(|span| { | ||
| 707 | span.format.math != Some(true) && span.format.embedded_object != Some(true) | ||
| 708 | }) | ||
| 709 | } | ||
| 710 | |||
| 711 | /// Whether a split at `offset` would divide a hyperlink, which the writer refuses. | ||
| 712 | fn divides_link(text: &Paragraph, offset: u32) -> Result<bool, Error> { | ||
| 713 | let byte = text.byte_offset(offset)?; | ||
| 714 | let link = |span: &crate::page::text::Span| span.format.hyperlink == Some(true); | ||
| 715 | let spans = text.spans(); | ||
| 716 | let mut start = 0; | ||
| 717 | for (i, span) in spans.iter().enumerate() { | ||
| 718 | if start < byte && byte < span.end { | ||
| 719 | return Ok(link(span)); | ||
| 720 | } | ||
| 721 | if byte == span.end && byte > 0 && i + 1 < spans.len() { | ||
| 722 | let next = &spans[i + 1]; | ||
| 723 | return Ok(link(span) && link(next) && !text.text()[byte..].starts_with('\u{fddf}')); | ||
| 724 | } | ||
| 725 | start = span.end; | ||
| 726 | } | ||
| 727 | Ok(false) | ||
| 728 | } | ||
| 729 | |||
| 730 | /// A paragraph as `Insert` and `Add` create it: structure and content, without the | ||
| 731 | /// properties their own ops set. | ||
| 732 | fn bare(paragraph: &PageParagraph) -> PageParagraph { | ||
| 733 | let mut stripped = paragraph.clone(); | ||
| 734 | stripped.style = None; | ||
| 735 | stripped.lists.clear(); | ||
| 736 | stripped.tags.clear(); | ||
| 737 | stripped.collapsed = false; | ||
| 738 | match &mut stripped.content { | ||
| 739 | ParagraphContent::Text(text) => text.tags.clear(), | ||
| 740 | ParagraphContent::Table(table) => { | ||
| 741 | for cell in table.rows.iter_mut().flat_map(|row| &mut row.cells) { | ||
| 742 | cell.paragraphs = cell.paragraphs.iter().map(bare).collect(); | ||
| 743 | } | ||
| 744 | } | ||
| 745 | _ => {} | ||
| 746 | } | ||
| 747 | stripped | ||
| 748 | } | ||
| 749 | |||
| 750 | fn bare_cells(cells: &[TableCell]) -> Vec<TableCell> { | ||
| 751 | cells | ||
| 752 | .iter() | ||
| 753 | .map(|cell| TableCell { | ||
| 754 | paragraphs: cell.paragraphs.iter().map(bare).collect(), | ||
| 755 | ..cell.clone() | ||
| 756 | }) | ||
| 757 | .collect() | ||
| 758 | } | ||
| 759 | |||
| 760 | struct Lowering { | ||
| 761 | ops: Vec<PageOp>, | ||
| 762 | /// The page as the ops so far leave it. | ||
| 763 | current: Page, | ||
| 764 | /// The paragraph after a partial model's range: its container and identity. | ||
| 765 | tail: Option<(ExGuid, ExGuid)>, | ||
| 766 | /// Paragraphs and text objects the ops so far created. | ||
| 767 | created: BTreeSet<ExGuid>, | ||
| 768 | } | ||
| 769 | |||
| 770 | impl Lowering { | ||
| 771 | fn new(current: Page, tail: Option<(ExGuid, ExGuid)>) -> Self { | ||
| 772 | Self { | ||
| 773 | ops: Vec::new(), | ||
| 774 | current, | ||
| 775 | tail, | ||
| 776 | created: BTreeSet::new(), | ||
| 777 | } | ||
| 778 | } | ||
| 779 | |||
| 780 | fn emit(&mut self, op: PageOp) -> Result<(), Error> { | ||
| 781 | model::apply(&mut self.current, &op)?; | ||
| 782 | self.ops.push(op); | ||
| 783 | Ok(()) | ||
| 784 | } | ||
| 785 | |||
| 786 | /// Whether the page, as the ops so far leave it, holds `id` as a paragraph, text, | ||
| 787 | /// list node or table part. | ||
| 788 | fn holds(&self, id: ExGuid) -> bool { | ||
| 789 | model::holds(&self.current, id) | ||
| 790 | } | ||
| 791 | |||
| 792 | fn run( | ||
| 793 | mut self, | ||
| 794 | before: &Page, | ||
| 795 | after: &Page, | ||
| 796 | old: &View<'_>, | ||
| 797 | new: &View<'_>, | ||
| 798 | ) -> Result<Vec<PageOp>, Error> { | ||
| 799 | validate(before, after, old, new)?; | ||
| 800 | let mut placed: BTreeMap<ExGuid, Vec<ExGuid>> = old.children.clone(); | ||
| 801 | let mut consumed = BTreeSet::new(); | ||
| 802 | self.split_and_join(old, new, &mut placed, &mut consumed)?; | ||
| 803 | self.place_page(old, new)?; | ||
| 804 | self.place(old, new, &placed)?; | ||
| 805 | self.delete(old, new, &consumed)?; | ||
| 806 | self.levels(new)?; | ||
| 807 | self.equations(new)?; | ||
| 808 | self.text(new)?; | ||
| 809 | self.styles(after, new)?; | ||
| 810 | self.lists(after, new)?; | ||
| 811 | self.tags(after, new)?; | ||
| 812 | self.paragraph_formatting(new)?; | ||
| 813 | self.formatting(old, new)?; | ||
| 814 | self.layout(old, new)?; | ||
| 815 | Ok(self.ops) | ||
| 816 | } | ||
| 817 | |||
| 818 | /// Splits and joins stored paragraphs as OneNote's Enter, Backspace and Delete do. | ||
| 819 | fn split_and_join( | ||
| 820 | &mut self, | ||
| 821 | old: &View<'_>, | ||
| 822 | new: &View<'_>, | ||
| 823 | placed: &mut BTreeMap<ExGuid, Vec<ExGuid>>, | ||
| 824 | consumed: &mut BTreeSet<ExGuid>, | ||
| 825 | ) -> Result<(), Error> { | ||
| 826 | // A new paragraph may be split from the stored paragraph nearest before it, whatever | ||
| 827 | // placement or pasted paragraphs the same edit adds between them. | ||
| 828 | let mut splits = Vec::new(); | ||
| 829 | for document in new.documents() { | ||
| 830 | let mut left = None; | ||
| 831 | for paragraph in document { | ||
| 832 | if old.paragraphs.contains_key(&paragraph.id) { | ||
| 833 | left = paragraph.text().is_some().then_some(paragraph.id); | ||
| 834 | } else if let Some(left) = left { | ||
| 835 | let container = new.container[&paragraph.id]; | ||
| 836 | let at = new.children[&container] | ||
| 837 | .iter() | ||
| 838 | .position(|id| *id == paragraph.id); | ||
| 839 | splits.push(((container, at), left, paragraph.id)); | ||
| 840 | } | ||
| 841 | } | ||
| 842 | } | ||
| 843 | splits.sort(); | ||
| 844 | let mut split = BTreeSet::new(); | ||
| 845 | for (_, left, right) in splits { | ||
| 846 | let (Some(previous), Some(after), Some(next)) = | ||
| 847 | (old.text(left), new.text(left), new.text(right)) | ||
| 848 | else { | ||
| 849 | continue; | ||
| 850 | }; | ||
| 851 | if split.contains(&left) | ||
| 852 | || after.id != previous.id | ||
| 853 | || new.paragraphs[&right].style != old.paragraphs[&left].style | ||
| 854 | || !ordinary(previous) | ||
| 855 | || old.title_outlines.contains(&root(old, left)) | ||
| 856 | { | ||
| 857 | continue; | ||
| 858 | } | ||
| 859 | let Ok(offset) = after.text.utf16_offset(after.text.text().len()) else { | ||
| 860 | continue; | ||
| 861 | }; | ||
| 862 | let length = previous.text.utf16_offset(previous.text.text().len())?; | ||
| 863 | // Splitting at the end only differs from appending a paragraph by the copied | ||
| 864 | // paragraph style; without one, an appended empty paragraph is an insertion. | ||
| 865 | if offset == length && new.paragraphs[&right].style.is_none() { | ||
| 866 | continue; | ||
| 867 | } | ||
| 868 | let (Ok(head), Ok(tail)) = ( | ||
| 869 | previous.text.slice(0..offset), | ||
| 870 | previous.text.slice(offset..length), | ||
| 871 | ) else { | ||
| 872 | continue; | ||
| 873 | }; | ||
| 874 | // An emptied side takes the writer's insertion style, so only its text must agree. | ||
| 875 | let same = |expected: &Paragraph, actual: &Paragraph| { | ||
| 876 | expected.text() == actual.text() | ||
| 877 | && (expected.text().is_empty() || expected == actual) | ||
| 878 | }; | ||
| 879 | if !same(&head, &after.text) | ||
| 880 | || !same(&tail, &next.text) | ||
| 881 | || divides_link(&previous.text, offset)? | ||
| 882 | || old.paragraphs[&left].lists.len() > 251 | ||
| 883 | { | ||
| 884 | continue; | ||
| 885 | } | ||
| 886 | // A text object a join moved into the paragraph keeps its identity there. | ||
| 887 | let right_text = if self.holds(next.id) { | ||
| 888 | new_id().map_err(|_| invalid("System random source failed"))? | ||
| 889 | } else { | ||
| 890 | next.id | ||
| 891 | }; | ||
| 892 | // The split copies the left paragraph's list nodes in order; a node the model | ||
| 893 | // shares with a stored paragraph stays that paragraph's. | ||
| 894 | let stored_lists = old.paragraphs[&left].lists.len(); | ||
| 895 | let model_lists = &new.paragraphs[&right].lists; | ||
| 896 | let mut lists = Vec::new(); | ||
| 897 | for i in 0..stored_lists { | ||
| 898 | lists.push(match model_lists.get(i) { | ||
| 899 | Some(list) if model_lists.len() == stored_lists && !self.holds(*list) => *list, | ||
| 900 | _ => new_id().map_err(|_| invalid("System random source failed"))?, | ||
| 901 | }); | ||
| 902 | } | ||
| 903 | self.emit(PageOp::Split { | ||
| 904 | text: previous.id, | ||
| 905 | at: offset, | ||
| 906 | paragraph: right, | ||
| 907 | right: right_text, | ||
| 908 | lists, | ||
| 909 | })?; | ||
| 910 | self.created.extend([right, right_text]); | ||
| 911 | split.insert(left); | ||
| 912 | // An earlier split may have carried the left paragraph to its tail. | ||
| 913 | let list = placed | ||
| 914 | .values_mut() | ||
| 915 | .find(|list| list.contains(&left)) | ||
| 916 | .unwrap(); | ||
| 917 | let at = list.iter().position(|id| *id == left).unwrap(); | ||
| 918 | list.insert(at + 1, right); | ||
| 919 | placed.insert(right, placed[&left].clone()); | ||
| 920 | placed.insert(left, Vec::new()); | ||
| 921 | } | ||
| 922 | for (container, children) in &old.children { | ||
| 923 | if !new.children.contains_key(container) { | ||
| 924 | continue; | ||
| 925 | } | ||
| 926 | for pair in children.windows(2) { | ||
| 927 | let [left, right] = [pair[0], pair[1]]; | ||
| 928 | let (Some(previous), Some(after), Some(removed)) = | ||
| 929 | (old.text(left), new.text(left), old.text(right)) | ||
| 930 | else { | ||
| 931 | continue; | ||
| 932 | }; | ||
| 933 | if new.paragraphs.contains_key(&right) | ||
| 934 | || !old.children[&left].is_empty() | ||
| 935 | || !old.children[&right].is_empty() | ||
| 936 | || removed.text.text().is_empty() | ||
| 937 | || !ordinary(previous) | ||
| 938 | || !ordinary(removed) | ||
| 939 | || old.title_outlines.contains(&root(old, left)) | ||
| 940 | { | ||
| 941 | continue; | ||
| 942 | } | ||
| 943 | let mut joined = previous.text.clone(); | ||
| 944 | if joined.append(removed.text.clone()).is_err() || joined != after.text { | ||
| 945 | continue; | ||
| 946 | } | ||
| 947 | let adopts_right = previous.text.text().is_empty(); | ||
| 948 | if after.id != if adopts_right { removed.id } else { previous.id } { | ||
| 949 | continue; | ||
| 950 | } | ||
| 951 | self.emit(PageOp::Join { | ||
| 952 | left: previous.id, | ||
| 953 | right: removed.id, | ||
| 954 | })?; | ||
| 955 | consumed.insert(right); | ||
| 956 | placed.get_mut(container).unwrap().retain(|id| *id != right); | ||
| 957 | placed.remove(&right); | ||
| 958 | } | ||
| 959 | } | ||
| 960 | Ok(()) | ||
| 961 | } | ||
| 962 | |||
| 963 | /// Adds, changes and orders what the page holds directly: outlines, pictures and ink. | ||
| 964 | fn place_page(&mut self, old: &View<'_>, new: &View<'_>) -> Result<(), Error> { | ||
| 965 | let pictures = |page: &Page| -> BTreeMap<ExGuid, crate::page::Image> { | ||
| 966 | page.objects | ||
| 967 | .iter() | ||
| 968 | .filter_map(|object| match object { | ||
| 969 | PageObject::Image(image) => Some((image.id, image.clone())), | ||
| 970 | _ => None, | ||
| 971 | }) | ||
| 972 | .collect() | ||
| 973 | }; | ||
| 974 | let stored = pictures(old.page); | ||
| 975 | for (id, image) in pictures(new.page) { | ||
| 976 | if let Some(previous) = stored.get(&id) | ||
| 977 | && *previous != image | ||
| 978 | { | ||
| 979 | super::content::picture_fixed_fields(previous, &image)?; | ||
| 980 | self.emit(PageOp::Picture { | ||
| 981 | picture: id, | ||
| 982 | layout: image.layout.clone(), | ||
| 983 | alt: image.alt.clone(), | ||
| 984 | })?; | ||
| 985 | } | ||
| 986 | } | ||
| 987 | let inks = |page: &Page| -> BTreeMap<ExGuid, crate::page::Ink> { | ||
| 988 | page.objects | ||
| 989 | .iter() | ||
| 990 | .filter_map(|object| match object { | ||
| 991 | PageObject::Ink(ink) => Some((ink.id, ink.clone())), | ||
| 992 | _ => None, | ||
| 993 | }) | ||
| 994 | .collect() | ||
| 995 | }; | ||
| 996 | let stored = inks(old.page); | ||
| 997 | for (id, ink) in inks(new.page) { | ||
| 998 | if let Some(previous) = stored.get(&id) | ||
| 999 | && *previous != ink | ||
| 1000 | { | ||
| 1001 | self.strokes(previous, &ink)?; | ||
| 1002 | } | ||
| 1003 | } | ||
| 1004 | let title = |page: &Page, id: ExGuid| { | ||
| 1005 | page.objects | ||
| 1006 | .iter() | ||
| 1007 | .any(|object| matches!(object, PageObject::Title(title) if title.id == id)) | ||
| 1008 | }; | ||
| 1009 | let survivors: Vec<ExGuid> = old | ||
| 1010 | .page_children | ||
| 1011 | .iter() | ||
| 1012 | .copied() | ||
| 1013 | .filter(|id| { | ||
| 1014 | !title(old.page, *id) && new.page.objects.iter().any(|object| object.id() == *id) | ||
| 1015 | }) | ||
| 1016 | .collect(); | ||
| 1017 | let after: Vec<&PageObject> = new | ||
| 1018 | .page | ||
| 1019 | .objects | ||
| 1020 | .iter() | ||
| 1021 | .filter(|object| !title(new.page, object.id())) | ||
| 1022 | .collect(); | ||
| 1023 | let order: Vec<ExGuid> = after.iter().map(|object| object.id()).collect(); | ||
| 1024 | let kept = kept_set(&survivors, &order, |id| { | ||
| 1025 | new.outlines.contains_key(&id) && !new.title_outlines.contains(&id) | ||
| 1026 | })?; | ||
| 1027 | let mut next = None; | ||
| 1028 | for object in after.iter().rev() { | ||
| 1029 | let id = object.id(); | ||
| 1030 | let stored = old.page_children.contains(&id); | ||
| 1031 | if !stored { | ||
| 1032 | if new.title_outlines.contains(&id) { | ||
| 1033 | return Err(invalid("Title outlines cannot be added")); | ||
| 1034 | } | ||
| 1035 | let object = match object { | ||
| 1036 | PageObject::Outline(outline) => { | ||
| 1037 | if outline.paragraphs.iter().any(|p| old.paragraphs.contains_key(&p.id)) { | ||
| 1038 | return Err(invalid( | ||
| 1039 | "A new outline holds new paragraphs; move stored ones after", | ||
| 1040 | )); | ||
| 1041 | } | ||
| 1042 | self.created.extend(model::identities(&outline.paragraphs)); | ||
| 1043 | PageObject::Outline(Outline { | ||
| 1044 | paragraphs: outline.paragraphs.iter().map(bare).collect(), | ||
| 1045 | ..(*outline).clone() | ||
| 1046 | }) | ||
| 1047 | } | ||
| 1048 | PageObject::Image(_) | PageObject::Ink(_) => (*object).clone(), | ||
| 1049 | PageObject::Title(_) | PageObject::Unsupported(_) => { | ||
| 1050 | return Err(invalid( | ||
| 1051 | "Titles and unsupported objects cannot be edited through the page model", | ||
| 1052 | )); | ||
| 1053 | } | ||
| 1054 | }; | ||
| 1055 | self.emit(PageOp::Add { | ||
| 1056 | object, | ||
| 1057 | before: next, | ||
| 1058 | })?; | ||
| 1059 | } else if !kept.contains(&id) { | ||
| 1060 | self.emit(PageOp::Move { | ||
| 1061 | object: id, | ||
| 1062 | parent: None, | ||
| 1063 | before: next, | ||
| 1064 | })?; | ||
| 1065 | } | ||
| 1066 | next = Some(id); | ||
| 1067 | } | ||
| 1068 | Ok(()) | ||
| 1069 | } | ||
| 1070 | |||
| 1071 | /// Inserts new paragraphs and moves stored ones to the containers and order the model | ||
| 1072 | /// gives them, then edits the stored tables and paragraph content. | ||
| 1073 | fn place( | ||
| 1074 | &mut self, | ||
| 1075 | old: &View<'_>, | ||
| 1076 | new: &View<'_>, | ||
| 1077 | placed: &BTreeMap<ExGuid, Vec<ExGuid>>, | ||
| 1078 | ) -> Result<(), Error> { | ||
| 1079 | let mut inserted: BTreeSet<ExGuid> = BTreeSet::new(); | ||
| 1080 | // New outlines and cells, whose paragraphs arrive with them. | ||
| 1081 | let arriving: BTreeSet<ExGuid> = new | ||
| 1082 | .children | ||
| 1083 | .keys() | ||
| 1084 | .filter(|id| { | ||
| 1085 | !old.children.contains_key(id) | ||
| 1086 | && !new.paragraphs.contains_key(id) | ||
| 1087 | && !new.external.contains(id) | ||
| 1088 | }) | ||
| 1089 | .copied() | ||
| 1090 | .collect(); | ||
| 1091 | for container in new.containers() { | ||
| 1092 | if arriving.contains(&root(new, container)) { | ||
| 1093 | continue; | ||
| 1094 | } | ||
| 1095 | let Some(children) = new.children.get(&container) else { | ||
| 1096 | continue; | ||
| 1097 | }; | ||
| 1098 | let current: Vec<ExGuid> = placed | ||
| 1099 | .get(&container) | ||
| 1100 | .map(|list| { | ||
| 1101 | list.iter() | ||
| 1102 | .copied() | ||
| 1103 | .filter(|id| new.paragraphs.contains_key(id)) | ||
| 1104 | .collect() | ||
| 1105 | }) | ||
| 1106 | .unwrap_or_default(); | ||
| 1107 | let kept = kept_set(&current, children, |_| true)?; | ||
| 1108 | let tail = self | ||
| 1109 | .tail | ||
| 1110 | .filter(|(parent, _)| *parent == container) | ||
| 1111 | .map(|(_, id)| id); | ||
| 1112 | let mut next = tail; | ||
| 1113 | let mut run: Vec<ExGuid> = Vec::new(); | ||
| 1114 | for id in children.iter().rev() { | ||
| 1115 | let fresh = !old.paragraphs.contains_key(id) && !self.created.contains(id); | ||
| 1116 | if fresh && !inserted.contains(id) { | ||
| 1117 | run.push(*id); | ||
| 1118 | continue; | ||
| 1119 | } | ||
| 1120 | self.insert(container, next, &mut run, new, &mut inserted)?; | ||
| 1121 | if !kept.contains(id) && !inserted.contains(id) { | ||
| 1122 | self.emit(PageOp::Move { | ||
| 1123 | object: *id, | ||
| 1124 | parent: Some(container), | ||
| 1125 | before: next, | ||
| 1126 | })?; | ||
| 1127 | } | ||
| 1128 | next = Some(*id); | ||
| 1129 | } | ||
| 1130 | self.insert(container, next, &mut run, new, &mut inserted)?; | ||
| 1131 | } | ||
| 1132 | self.tables(old, new)?; | ||
| 1133 | self.content(old, new)?; | ||
| 1134 | Ok(()) | ||
| 1135 | } | ||
| 1136 | |||
| 1137 | /// Inserts `run`, new siblings in reverse order, with their new descendants. | ||
| 1138 | fn insert( | ||
| 1139 | &mut self, | ||
| 1140 | container: ExGuid, | ||
| 1141 | before: Option<ExGuid>, | ||
| 1142 | run: &mut Vec<ExGuid>, | ||
| 1143 | new: &View<'_>, | ||
| 1144 | inserted: &mut BTreeSet<ExGuid>, | ||
| 1145 | ) -> Result<(), Error> { | ||
| 1146 | if run.is_empty() { | ||
| 1147 | return Ok(()); | ||
| 1148 | } | ||
| 1149 | run.reverse(); | ||
| 1150 | let mut paragraphs = Vec::new(); | ||
| 1151 | let mut pending: Vec<ExGuid> = run.drain(..).rev().collect(); | ||
| 1152 | while let Some(id) = pending.pop() { | ||
| 1153 | let paragraph = new.paragraphs[&id]; | ||
| 1154 | paragraphs.push(bare(paragraph)); | ||
| 1155 | inserted.insert(id); | ||
| 1156 | self.created.insert(id); | ||
| 1157 | if let Some(text) = paragraph.text() { | ||
| 1158 | self.created.insert(text.id); | ||
| 1159 | } | ||
| 1160 | if let ParagraphContent::Table(table) = &paragraph.content { | ||
| 1161 | for cell in table.rows.iter().flat_map(|row| &row.cells) { | ||
| 1162 | self.created.extend(model::identities(&cell.paragraphs)); | ||
| 1163 | } | ||
| 1164 | } | ||
| 1165 | // New children follow their parent; stored ones move there later. | ||
| 1166 | pending.extend( | ||
| 1167 | new.children[&id] | ||
| 1168 | .iter() | ||
| 1169 | .rev() | ||
| 1170 | .filter(|child| !self.holds(**child) && !inserted.contains(child)), | ||
| 1171 | ); | ||
| 1172 | } | ||
| 1173 | self.emit(PageOp::Insert { | ||
| 1174 | container, | ||
| 1175 | before, | ||
| 1176 | paragraphs, | ||
| 1177 | }) | ||
| 1178 | } | ||
| 1179 | |||
| 1180 | /// Row, column, width, border and cell edits of stored tables. | ||
| 1181 | fn tables(&mut self, old: &View<'_>, new: &View<'_>) -> Result<(), Error> { | ||
| 1182 | let stored: BTreeMap<ExGuid, &Table> = old | ||
| 1183 | .paragraphs | ||
| 1184 | .values() | ||
| 1185 | .filter_map(|p| match &p.content { | ||
| 1186 | ParagraphContent::Table(table) => Some((table.id, table)), | ||
| 1187 | _ => None, | ||
| 1188 | }) | ||
| 1189 | .collect(); | ||
| 1190 | for paragraph in new.paragraphs.values() { | ||
| 1191 | let ParagraphContent::Table(table) = &paragraph.content else { | ||
| 1192 | continue; | ||
| 1193 | }; | ||
| 1194 | let Some(previous) = stored.get(&table.id) else { | ||
| 1195 | continue; | ||
| 1196 | }; | ||
| 1197 | self.table(previous, table)?; | ||
| 1198 | let cells: BTreeMap<ExGuid, &TableCell> = previous | ||
| 1199 | .rows | ||
| 1200 | .iter() | ||
| 1201 | .flat_map(|row| &row.cells) | ||
| 1202 | .map(|cell| (cell.id, cell)) | ||
| 1203 | .collect(); | ||
| 1204 | for cell in table.rows.iter().flat_map(|row| &row.cells) { | ||
| 1205 | let Some(before) = cells.get(&cell.id) else { | ||
| 1206 | continue; | ||
| 1207 | }; | ||
| 1208 | if (cell.shading, &cell.indents) != (before.shading, &before.indents) { | ||
| 1209 | self.emit(PageOp::Table { | ||
| 1210 | table: table.id, | ||
| 1211 | edit: TableEdit::Cell { | ||
| 1212 | cell: cell.id, | ||
| 1213 | shading: cell.shading, | ||
| 1214 | indents: cell.indents.clone(), | ||
| 1215 | }, | ||
| 1216 | })?; | ||
| 1217 | } | ||
| 1218 | } | ||
| 1219 | } | ||
| 1220 | Ok(()) | ||
| 1221 | } | ||
| 1222 | |||
| 1223 | /// The row and column edits turning stored table `before` into `after`. | ||
| 1224 | fn table(&mut self, before: &Table, after: &Table) -> Result<(), Error> { | ||
| 1225 | let id = after.id; | ||
| 1226 | let unsupported = || invalid("Table rows and cells keep their order; add or remove them"); | ||
| 1227 | let old_rows: Vec<ExGuid> = before.rows.iter().map(|row| row.id).collect(); | ||
| 1228 | let new_rows: Vec<ExGuid> = after.rows.iter().map(|row| row.id).collect(); | ||
| 1229 | let kept_rows: Vec<ExGuid> = old_rows | ||
| 1230 | .iter() | ||
| 1231 | .copied() | ||
| 1232 | .filter(|row| new_rows.contains(row)) | ||
| 1233 | .collect(); | ||
| 1234 | if new_rows | ||
| 1235 | .iter() | ||
| 1236 | .filter(|row| old_rows.contains(row)) | ||
| 1237 | .ne(kept_rows.iter()) | ||
| 1238 | { | ||
| 1239 | return Err(unsupported()); | ||
| 1240 | } | ||
| 1241 | // The column pattern every kept row shares: which stored cells stay, which are new. | ||
| 1242 | let mut deleted: Option<Vec<u32>> = None; | ||
| 1243 | let mut added: Option<Vec<u32>> = None; | ||
| 1244 | for row in &kept_rows { | ||
| 1245 | let old_cells: Vec<ExGuid> = before.rows.iter().find(|r| r.id == *row).unwrap().cells | ||
| 1246 | .iter() | ||
| 1247 | .map(|cell| cell.id) | ||
| 1248 | .collect(); | ||
| 1249 | let new_cells: Vec<ExGuid> = after.rows.iter().find(|r| r.id == *row).unwrap().cells | ||
| 1250 | .iter() | ||
| 1251 | .map(|cell| cell.id) | ||
| 1252 | .collect(); | ||
| 1253 | let gone: Vec<u32> = (0..old_cells.len() as u32) | ||
| 1254 | .filter(|i| !new_cells.contains(&old_cells[*i as usize])) | ||
| 1255 | .collect(); | ||
| 1256 | let fresh: Vec<u32> = (0..new_cells.len() as u32) | ||
| 1257 | .filter(|i| !old_cells.contains(&new_cells[*i as usize])) | ||
| 1258 | .collect(); | ||
| 1259 | if old_cells | ||
| 1260 | .iter() | ||
| 1261 | .filter(|cell| new_cells.contains(cell)) | ||
| 1262 | .ne(new_cells.iter().filter(|cell| old_cells.contains(cell))) | ||
| 1263 | || deleted.as_ref().is_some_and(|known| *known != gone) | ||
| 1264 | || added.as_ref().is_some_and(|known| *known != fresh) | ||
| 1265 | { | ||
| 1266 | return Err(unsupported()); | ||
| 1267 | } | ||
| 1268 | deleted = Some(gone); | ||
| 1269 | added = Some(fresh); | ||
| 1270 | } | ||
| 1271 | let (deleted, added) = (deleted.unwrap_or_default(), added.unwrap_or_default()); | ||
| 1272 | if kept_rows.is_empty() && before.columns.len() != after.columns.len() { | ||
| 1273 | return Err(unsupported()); | ||
| 1274 | } | ||
| 1275 | for row in &old_rows { | ||
| 1276 | if !new_rows.contains(row) && !kept_rows.is_empty() { | ||
| 1277 | self.emit(PageOp::Table { | ||
| 1278 | table: id, | ||
| 1279 | edit: TableEdit::DeleteRow(*row), | ||
| 1280 | })?; | ||
| 1281 | } | ||
| 1282 | } | ||
| 1283 | for at in deleted.iter().rev() { | ||
| 1284 | self.emit(PageOp::Table { | ||
| 1285 | table: id, | ||
| 1286 | edit: TableEdit::DeleteColumn(*at), | ||
| 1287 | })?; | ||
| 1288 | } | ||
| 1289 | for at in &added { | ||
| 1290 | let cells = kept_rows | ||
| 1291 | .iter() | ||
| 1292 | .map(|row| { | ||
| 1293 | let row = after.rows.iter().find(|r| r.id == *row).unwrap(); | ||
| 1294 | bare_cells(&row.cells[*at as usize..=*at as usize]).remove(0) | ||
| 1295 | }) | ||
| 1296 | .collect::<Vec<_>>(); | ||
| 1297 | for cell in &cells { | ||
| 1298 | self.created.extend(model::identities(&cell.paragraphs)); | ||
| 1299 | } | ||
| 1300 | self.emit(PageOp::Table { | ||
| 1301 | table: id, | ||
| 1302 | edit: TableEdit::Column { | ||
| 1303 | at: *at, | ||
| 1304 | width: after.columns[*at as usize].width, | ||
| 1305 | cells, | ||
| 1306 | }, | ||
| 1307 | })?; | ||
| 1308 | } | ||
| 1309 | // New rows, each run before the stored row that follows it. | ||
| 1310 | let mut run = Vec::new(); | ||
| 1311 | let mut next = None; | ||
| 1312 | for row in after.rows.iter().rev() { | ||
| 1313 | if old_rows.contains(&row.id) { | ||
| 1314 | self.rows(id, next, &mut run)?; | ||
| 1315 | next = Some(row.id); | ||
| 1316 | } else { | ||
| 1317 | run.push(row.clone()); | ||
| 1318 | } | ||
| 1319 | } | ||
| 1320 | self.rows(id, next, &mut run)?; | ||
| 1321 | if kept_rows.is_empty() { | ||
| 1322 | for row in &old_rows { | ||
| 1323 | self.emit(PageOp::Table { | ||
| 1324 | table: id, | ||
| 1325 | edit: TableEdit::DeleteRow(*row), | ||
| 1326 | })?; | ||
| 1327 | } | ||
| 1328 | } | ||
| 1329 | let current = model::table(&self.current, id) | ||
| 1330 | .ok_or_else(|| invalid("A table is missing after row edits"))?; | ||
| 1331 | if current.columns != after.columns { | ||
| 1332 | self.emit(PageOp::Table { | ||
| 1333 | table: id, | ||
| 1334 | edit: TableEdit::Columns(after.columns.clone()), | ||
| 1335 | })?; | ||
| 1336 | } | ||
| 1337 | let current = model::table(&self.current, id).unwrap(); | ||
| 1338 | if current.borders != after.borders | ||
| 1339 | && let Some(borders) = after.borders | ||
| 1340 | { | ||
| 1341 | self.emit(PageOp::Table { | ||
| 1342 | table: id, | ||
| 1343 | edit: TableEdit::Borders(borders), | ||
| 1344 | })?; | ||
| 1345 | } | ||
| 1346 | Ok(()) | ||
| 1347 | } | ||
| 1348 | |||
| 1349 | fn rows( | ||
| 1350 | &mut self, | ||
| 1351 | table: ExGuid, | ||
| 1352 | before: Option<ExGuid>, | ||
| 1353 | run: &mut Vec<crate::page::TableRow>, | ||
| 1354 | ) -> Result<(), Error> { | ||
| 1355 | if run.is_empty() { | ||
| 1356 | return Ok(()); | ||
| 1357 | } | ||
| 1358 | run.reverse(); | ||
| 1359 | let rows: Vec<crate::page::TableRow> = run | ||
| 1360 | .drain(..) | ||
| 1361 | .map(|row| crate::page::TableRow { | ||
| 1362 | id: row.id, | ||
| 1363 | cells: bare_cells(&row.cells), | ||
| 1364 | }) | ||
| 1365 | .collect(); | ||
| 1366 | for cell in rows.iter().flat_map(|row| &row.cells) { | ||
| 1367 | self.created.extend(model::identities(&cell.paragraphs)); | ||
| 1368 | } | ||
| 1369 | self.emit(PageOp::Table { | ||
| 1370 | table, | ||
| 1371 | edit: TableEdit::Rows { before, rows }, | ||
| 1372 | }) | ||
| 1373 | } | ||
| 1374 | |||
| 1375 | /// Edits of stored pictures, ink and attachments that paragraphs hold. | ||
| 1376 | fn content(&mut self, old: &View<'_>, new: &View<'_>) -> Result<(), Error> { | ||
| 1377 | for (id, paragraph) in &new.paragraphs { | ||
| 1378 | let Some(previous) = old.paragraphs.get(id) else { | ||
| 1379 | continue; | ||
| 1380 | }; | ||
| 1381 | match (&previous.content, &paragraph.content) { | ||
| 1382 | (ParagraphContent::Image(stored), ParagraphContent::Image(image)) | ||
| 1383 | if stored != image => | ||
| 1384 | { | ||
| 1385 | self.emit(PageOp::Picture { | ||
| 1386 | picture: image.id, | ||
| 1387 | layout: image.layout.clone(), | ||
| 1388 | alt: image.alt.clone(), | ||
| 1389 | })?; | ||
| 1390 | } | ||
| 1391 | (ParagraphContent::Ink(stored), ParagraphContent::Ink(ink)) if stored != ink => { | ||
| 1392 | self.strokes(stored, ink)?; | ||
| 1393 | } | ||
| 1394 | (ParagraphContent::Attachment(stored), ParagraphContent::Attachment(attachment)) | ||
| 1395 | if stored != attachment => | ||
| 1396 | { | ||
| 1397 | self.emit(PageOp::Attachment { | ||
| 1398 | attachment: attachment.id, | ||
| 1399 | filename: attachment.filename.clone(), | ||
| 1400 | source_path: attachment.source_path.clone(), | ||
| 1401 | size: attachment.size, | ||
| 1402 | })?; | ||
| 1403 | } | ||
| 1404 | _ => {} | ||
| 1405 | } | ||
| 1406 | } | ||
| 1407 | Ok(()) | ||
| 1408 | } | ||
| 1409 | |||
| 1410 | fn strokes(&mut self, stored: &crate::page::Ink, ink: &crate::page::Ink) -> Result<(), Error> { | ||
| 1411 | if stored.layout != ink.layout || stored.groups != ink.groups { | ||
| 1412 | return Err(invalid("Ink position and groups stay as stored")); | ||
| 1413 | } | ||
| 1414 | let mut add = Vec::new(); | ||
| 1415 | for stroke in &ink.strokes { | ||
| 1416 | match stored.strokes.iter().find(|s| s.id == stroke.id) { | ||
| 1417 | Some(previous) if previous == stroke => {} | ||
| 1418 | Some(_) => return Err(invalid("A stored stroke keeps its path and pen")), | ||
| 1419 | None => add.push(stroke.clone()), | ||
| 1420 | } | ||
| 1421 | } | ||
| 1422 | let remove: Vec<ExGuid> = stored | ||
| 1423 | .strokes | ||
| 1424 | .iter() | ||
| 1425 | .filter(|stroke| !ink.strokes.iter().any(|s| s.id == stroke.id)) | ||
| 1426 | .map(|stroke| stroke.id) | ||
| 1427 | .collect(); | ||
| 1428 | if ink.strokes.iter().map(|s| s.id).ne(stored | ||
| 1429 | .strokes | ||
| 1430 | .iter() | ||
| 1431 | .filter(|s| !remove.contains(&s.id)) | ||
| 1432 | .map(|s| s.id) | ||
| 1433 | .chain(add.iter().map(|s| s.id))) | ||
| 1434 | { | ||
| 1435 | return Err(invalid("Stored strokes keep their order; new ones follow")); | ||
| 1436 | } | ||
| 1437 | self.emit(PageOp::Strokes { | ||
| 1438 | ink: ink.id, | ||
| 1439 | add, | ||
| 1440 | remove, | ||
| 1441 | }) | ||
| 1442 | } | ||
| 1443 | |||
| 1444 | fn delete( | ||
| 1445 | &mut self, | ||
| 1446 | old: &View<'_>, | ||
| 1447 | new: &View<'_>, | ||
| 1448 | consumed: &BTreeSet<ExGuid>, | ||
| 1449 | ) -> Result<(), Error> { | ||
| 1450 | let removed_outline = | ||
| 1451 | |id: ExGuid| old.outlines.contains_key(&id) && !new.outlines.contains_key(&id); | ||
| 1452 | for id in old.paragraphs.keys() { | ||
| 1453 | if new.paragraphs.contains_key(id) || consumed.contains(id) { | ||
| 1454 | continue; | ||
| 1455 | } | ||
| 1456 | let mut ancestor = old.container[id]; | ||
| 1457 | let mut covered = false; | ||
| 1458 | // A surviving ancestor is placed where the model says, carrying this paragraph along. | ||
| 1459 | while !new.paragraphs.contains_key(&ancestor) { | ||
| 1460 | if removed_outline(ancestor) | ||
| 1461 | || (old.paragraphs.contains_key(&ancestor) && !consumed.contains(&ancestor)) | ||
| 1462 | || (old.children.contains_key(&ancestor) | ||
| 1463 | && !old.paragraphs.contains_key(&ancestor) | ||
| 1464 | && !old.outlines.contains_key(&ancestor) | ||
| 1465 | && !new.children.contains_key(&ancestor) | ||
| 1466 | && !old.external.contains(&ancestor)) | ||
| 1467 | { | ||
| 1468 | covered = true; | ||
| 1469 | break; | ||
| 1470 | } | ||
| 1471 | match old.container.get(&ancestor) { | ||
| 1472 | Some(parent) => ancestor = *parent, | ||
| 1473 | None => break, | ||
| 1474 | } | ||
| 1475 | } | ||
| 1476 | if !covered { | ||
| 1477 | self.emit(PageOp::Delete { object: *id })?; | ||
| 1478 | } | ||
| 1479 | } | ||
| 1480 | let kept: BTreeSet<ExGuid> = new.page.objects.iter().map(PageObject::id).collect(); | ||
| 1481 | for object in &old.page.objects { | ||
| 1482 | if matches!(object, PageObject::Image(_) | PageObject::Ink(_)) | ||
| 1483 | && !kept.contains(&object.id()) | ||
| 1484 | { | ||
| 1485 | self.emit(PageOp::Delete { | ||
| 1486 | object: object.id(), | ||
| 1487 | })?; | ||
| 1488 | } | ||
| 1489 | } | ||
| 1490 | for id in old.outlines.keys() { | ||
| 1491 | if removed_outline(*id) { | ||
| 1492 | if old.title_outlines.contains(id) { | ||
| 1493 | return Err(invalid("Title outlines cannot be removed")); | ||
| 1494 | } | ||
| 1495 | self.emit(PageOp::Delete { object: *id })?; | ||
| 1496 | } | ||
| 1497 | } | ||
| 1498 | Ok(()) | ||
| 1499 | } | ||
| 1500 | |||
| 1501 | /// Gives paragraphs the levels the model does, parents first. | ||
| 1502 | fn levels(&mut self, new: &View<'_>) -> Result<(), Error> { | ||
| 1503 | for document in new.documents() { | ||
| 1504 | for paragraph in document { | ||
| 1505 | let current = model::paragraph(&self.current, paragraph.id) | ||
| 1506 | .ok_or_else(|| invalid("A paragraph is missing after structural edits"))?; | ||
| 1507 | if current.level != paragraph.level { | ||
| 1508 | self.emit(PageOp::Level { | ||
| 1509 | paragraph: paragraph.id, | ||
| 1510 | level: paragraph.level, | ||
| 1511 | })?; | ||
| 1512 | } | ||
| 1513 | } | ||
| 1514 | } | ||
| 1515 | Ok(()) | ||
| 1516 | } | ||
| 1517 | |||
| 1518 | fn equations(&mut self, new: &View<'_>) -> Result<(), Error> { | ||
| 1519 | for (id, paragraph) in &new.paragraphs { | ||
| 1520 | let Some(text) = paragraph.text() else { | ||
| 1521 | continue; | ||
| 1522 | }; | ||
| 1523 | let stored = model::paragraph(&self.current, *id).and_then(|p| p.text().cloned()); | ||
| 1524 | // Deleting an equation leaves no math in the model, but its stored runs are still math. | ||
| 1525 | if !crate::page::Math::is_equation(&text.text) | ||
| 1526 | && !stored | ||
| 1527 | .as_ref() | ||
| 1528 | .is_some_and(|stored| crate::page::Math::is_equation(&stored.text)) | ||
| 1529 | { | ||
| 1530 | continue; | ||
| 1531 | } | ||
| 1532 | let stored = | ||
| 1533 | stored.ok_or_else(|| invalid("An equation paragraph is missing after placement"))?; | ||
| 1534 | if stored.text != text.text { | ||
| 1535 | self.emit(PageOp::Equation { | ||
| 1536 | text: stored.id, | ||
| 1537 | math: text.text.clone(), | ||
| 1538 | })?; | ||
| 1539 | } | ||
| 1540 | } | ||
| 1541 | Ok(()) | ||
| 1542 | } | ||
| 1543 | |||
| 1544 | fn text(&mut self, new: &View<'_>) -> Result<(), Error> { | ||
| 1545 | for (id, paragraph) in &new.paragraphs { | ||
| 1546 | let Some(text) = paragraph.text() else { | ||
| 1547 | continue; | ||
| 1548 | }; | ||
| 1549 | let Some(stored) = model::paragraph(&self.current, *id).and_then(|p| p.text().cloned()) | ||
| 1550 | else { | ||
| 1551 | return Err(invalid("A paragraph is missing after structural edits")); | ||
| 1552 | }; | ||
| 1553 | if stored.id != text.id { | ||
| 1554 | return Err(invalid("Text object identities cannot change")); | ||
| 1555 | } | ||
| 1556 | if stored.text.text() == text.text.text() { | ||
| 1557 | continue; | ||
| 1558 | } | ||
| 1559 | let (range, with) = text_edit(stored.text.text(), text.text.text())?; | ||
| 1560 | self.emit(PageOp::Text { | ||
| 1561 | text: stored.id, | ||
| 1562 | range, | ||
| 1563 | with, | ||
| 1564 | })?; | ||
| 1565 | } | ||
| 1566 | Ok(()) | ||
| 1567 | } | ||
| 1568 | |||
| 1569 | fn styles(&mut self, after: &Page, new: &View<'_>) -> Result<(), Error> { | ||
| 1570 | for (id, paragraph) in &new.paragraphs { | ||
| 1571 | let Some(style) = paragraph.style else { | ||
| 1572 | continue; | ||
| 1573 | }; | ||
| 1574 | let current = model::paragraph(&self.current, *id) | ||
| 1575 | .ok_or_else(|| invalid("A paragraph is missing after text edits"))?; | ||
| 1576 | if current.style == Some(style) { | ||
| 1577 | continue; | ||
| 1578 | } | ||
| 1579 | if paragraph.text().is_none() { | ||
| 1580 | return Err(invalid("Only text paragraphs take a paragraph style")); | ||
| 1581 | } | ||
| 1582 | let definition = after | ||
| 1583 | .definitions | ||
| 1584 | .get(&style) | ||
| 1585 | .ok_or_else(|| invalid("A paragraph references a missing style definition"))?; | ||
| 1586 | self.emit(PageOp::Style { | ||
| 1587 | paragraph: *id, | ||
| 1588 | style, | ||
| 1589 | definition: definition.clone(), | ||
| 1590 | })?; | ||
| 1591 | } | ||
| 1592 | Ok(()) | ||
| 1593 | } | ||
| 1594 | |||
| 1595 | /// Gives each paragraph the list nodes its model references; one another paragraph | ||
| 1596 | /// owns is copied, as native list nodes belong to one paragraph. | ||
| 1597 | fn lists(&mut self, after: &Page, new: &View<'_>) -> Result<(), Error> { | ||
| 1598 | for (id, paragraph) in &new.paragraphs { | ||
| 1599 | let current = model::paragraph(&self.current, *id) | ||
| 1600 | .ok_or_else(|| invalid("A paragraph is missing after text edits"))?; | ||
| 1601 | let same = paragraph.lists.len() == current.lists.len() | ||
| 1602 | && paragraph.lists.iter().zip(&current.lists).all(|(model, node)| { | ||
| 1603 | model == node | ||
| 1604 | && after.definitions.get(model) == self.current.definitions.get(node) | ||
| 1605 | }); | ||
| 1606 | if same { | ||
| 1607 | continue; | ||
| 1608 | } | ||
| 1609 | let own = current.lists.clone(); | ||
| 1610 | let mut lists = Vec::new(); | ||
| 1611 | for list in &paragraph.lists { | ||
| 1612 | let definition = after | ||
| 1613 | .definitions | ||
| 1614 | .get(list) | ||
| 1615 | .ok_or_else(|| invalid("A paragraph references a missing list definition"))?; | ||
| 1616 | let node = if own.contains(list) || !model::owns_list(&self.current, *list) { | ||
| 1617 | *list | ||
| 1618 | } else { | ||
| 1619 | new_id().map_err(|_| invalid("System random source failed"))? | ||
| 1620 | }; | ||
| 1621 | lists.push((node, definition.clone())); | ||
| 1622 | } | ||
| 1623 | self.emit(PageOp::List { | ||
| 1624 | paragraph: *id, | ||
| 1625 | lists, | ||
| 1626 | })?; | ||
| 1627 | } | ||
| 1628 | Ok(()) | ||
| 1629 | } | ||
| 1630 | |||
| 1631 | fn tags(&mut self, after: &Page, new: &View<'_>) -> Result<(), Error> { | ||
| 1632 | for (id, paragraph) in &new.paragraphs { | ||
| 1633 | let current = model::paragraph(&self.current, *id) | ||
| 1634 | .ok_or_else(|| invalid("A paragraph is missing after text edits"))? | ||
| 1635 | .clone(); | ||
| 1636 | let mut targets = vec![(*id, &paragraph.tags, current.tags.clone())]; | ||
| 1637 | if let (Some(text), Some(stored)) = (paragraph.text(), current.text()) { | ||
| 1638 | targets.push((text.id, &text.tags, stored.tags.clone())); | ||
| 1639 | } | ||
| 1640 | for (target, tags, stored) in targets { | ||
| 1641 | if same_tags(tags, &stored) { | ||
| 1642 | continue; | ||
| 1643 | } | ||
| 1644 | let mut definitions = Vec::new(); | ||
| 1645 | for tag in tags { | ||
| 1646 | let definition = tag | ||
| 1647 | .definition | ||
| 1648 | .ok_or_else(|| invalid("A note tag names its definition"))?; | ||
| 1649 | if let Some(model) = after.definitions.get(&definition) | ||
| 1650 | && !definitions.iter().any(|(id, _)| *id == definition) | ||
| 1651 | { | ||
| 1652 | definitions.push((definition, model.clone())); | ||
| 1653 | } | ||
| 1654 | } | ||
| 1655 | self.emit(PageOp::Tags { | ||
| 1656 | target, | ||
| 1657 | tags: tags.clone(), | ||
| 1658 | definitions, | ||
| 1659 | })?; | ||
| 1660 | } | ||
| 1661 | } | ||
| 1662 | Ok(()) | ||
| 1663 | } | ||
| 1664 | |||
| 1665 | fn paragraph_formatting(&mut self, new: &View<'_>) -> Result<(), Error> { | ||
| 1666 | for (id, paragraph) in &new.paragraphs { | ||
| 1667 | let Some(text) = paragraph.text() else { | ||
| 1668 | continue; | ||
| 1669 | }; | ||
| 1670 | let stored = model::paragraph(&self.current, *id) | ||
| 1671 | .and_then(|p| p.text().cloned()) | ||
| 1672 | .ok_or_else(|| invalid("A paragraph is missing after text edits"))?; | ||
| 1673 | let fields = paragraph_fields(&stored.text, &text.text)?; | ||
| 1674 | if fields == ParagraphFields::default() { | ||
| 1675 | continue; | ||
| 1676 | } | ||
| 1677 | if text.date_field.is_some() { | ||
| 1678 | return Err(invalid( | ||
| 1679 | "Generated title fields cannot be formatted as ordinary text", | ||
| 1680 | )); | ||
| 1681 | } | ||
| 1682 | self.emit(PageOp::Paragraph { | ||
| 1683 | paragraph: *id, | ||
| 1684 | alignment: fields.alignment, | ||
| 1685 | rtl: fields.rtl, | ||
| 1686 | space_before: fields.space_before, | ||
| 1687 | space_after: fields.space_after, | ||
| 1688 | line_spacing: fields.line_spacing, | ||
| 1689 | language: fields.language, | ||
| 1690 | })?; | ||
| 1691 | } | ||
| 1692 | Ok(()) | ||
| 1693 | } | ||
| 1694 | |||
| 1695 | fn formatting(&mut self, old: &View<'_>, new: &View<'_>) -> Result<(), Error> { | ||
| 1696 | let stored_texts: BTreeSet<ExGuid> = old | ||
| 1697 | .paragraphs | ||
| 1698 | .values() | ||
| 1699 | .filter_map(|p| Some(p.text()?.id)) | ||
| 1700 | .collect(); | ||
| 1701 | for (id, paragraph) in &new.paragraphs { | ||
| 1702 | let Some(text) = paragraph.text() else { | ||
| 1703 | continue; | ||
| 1704 | }; | ||
| 1705 | let stored = model::paragraph(&self.current, *id) | ||
| 1706 | .and_then(|p| p.text().cloned()) | ||
| 1707 | .ok_or_else(|| invalid("A paragraph is missing after text edits"))?; | ||
| 1708 | if stored.text.text() != text.text.text() { | ||
| 1709 | return Err(invalid("Text edits did not converge on the model")); | ||
| 1710 | } | ||
| 1711 | let fresh = !stored_texts.contains(&text.id); | ||
| 1712 | for (range, set, clear) in format_edits(&stored.text, &text.text, fresh)? { | ||
| 1713 | self.emit(PageOp::Format { | ||
| 1714 | text: stored.id, | ||
| 1715 | range, | ||
| 1716 | set, | ||
| 1717 | clear, | ||
| 1718 | })?; | ||
| 1719 | } | ||
| 1720 | } | ||
| 1721 | Ok(()) | ||
| 1722 | } | ||
| 1723 | |||
| 1724 | fn layout(&mut self, old: &View<'_>, new: &View<'_>) -> Result<(), Error> { | ||
| 1725 | for (id, paragraph) in &new.paragraphs { | ||
| 1726 | let current = model::paragraph(&self.current, *id) | ||
| 1727 | .ok_or_else(|| invalid("A paragraph is missing after text edits"))?; | ||
| 1728 | if current.collapsed != paragraph.collapsed { | ||
| 1729 | self.emit(PageOp::Outline { | ||
| 1730 | object: *id, | ||
| 1731 | edit: crate::OutlineEdit::Collapsed(paragraph.collapsed), | ||
| 1732 | })?; | ||
| 1733 | } | ||
| 1734 | } | ||
| 1735 | for (id, outline) in &new.outlines { | ||
| 1736 | if new.title_outlines.contains(id) || !old.outlines.contains_key(id) { | ||
| 1737 | continue; | ||
| 1738 | } | ||
| 1739 | let stored = model::outline(&self.current, *id) | ||
| 1740 | .ok_or_else(|| invalid("An outline is missing after text edits"))?; | ||
| 1741 | let (stored_position, stored_width) = ( | ||
| 1742 | (stored.layout.x, stored.layout.y), | ||
| 1743 | (stored.layout.max_width, stored.layout.width_set_by_user), | ||
| 1744 | ); | ||
| 1745 | if (outline.layout.x, outline.layout.y) != stored_position { | ||
| 1746 | let (Some(x), Some(y)) = (outline.layout.x, outline.layout.y) else { | ||
| 1747 | return Err(invalid("An outline position needs both coordinates")); | ||
| 1748 | }; | ||
| 1749 | self.emit(PageOp::Outline { | ||
| 1750 | object: *id, | ||
| 1751 | edit: crate::OutlineEdit::Position { x, y }, | ||
| 1752 | })?; | ||
| 1753 | } | ||
| 1754 | if (outline.layout.max_width, outline.layout.width_set_by_user) != stored_width { | ||
| 1755 | let Some(points) = outline.layout.max_width else { | ||
| 1756 | return Err(invalid("An outline width cannot be removed")); | ||
| 1757 | }; | ||
| 1758 | self.emit(PageOp::Outline { | ||
| 1759 | object: *id, | ||
| 1760 | edit: crate::OutlineEdit::Width { | ||
| 1761 | points, | ||
| 1762 | user_set: outline.layout.width_set_by_user == Some(true), | ||
| 1763 | }, | ||
| 1764 | })?; | ||
| 1765 | } | ||
| 1766 | } | ||
| 1767 | Ok(()) | ||
| 1768 | } | ||
| 1769 | } | ||
| 1770 | |||
| 1771 | /// The outline or cell a paragraph of `view` lies in. | ||
| 1772 | fn root(view: &View<'_>, paragraph: ExGuid) -> ExGuid { | ||
| 1773 | let mut at = paragraph; | ||
| 1774 | while let Some(parent) = view.container.get(&at) { | ||
| 1775 | at = *parent; | ||
| 1776 | } | ||
| 1777 | at | ||
| 1778 | } | ||
crates/onestore/src/op/mod.rs created+265| ... | @@ -0,0 +1,265 @@ | ||
| 1 | //! Object-level edits of a section: what an editor emits, a queue stores and | ||
| 2 | //! `Section::apply` writes. Identities of new objects are the emitter's, so applying an edit | ||
| 3 | //! to another image of the section creates the same objects; UTF-16 code units measure text. | ||
| 4 | |||
| 5 | use crate::{ | ||
| 6 | ExGuid, OutlineEdit, PageCreation, PageEdit, TextAttribute, | ||
| 7 | document::{Layout, Tag}, | ||
| 8 | page::{Definition, InkStroke, Page, PageObject, PageParagraph, Paragraph, TableCell, | ||
| 9 | TableColumn, TableRow}, | ||
| 10 | }; | ||
| 11 | use serde::{Deserialize, Serialize}; | ||
| 12 | use std::ops::Range; | ||
| 13 | |||
| 14 | mod apply; | ||
| 15 | pub(crate) mod content; | ||
| 16 | pub(crate) mod levels; | ||
| 17 | pub(crate) mod lower; | ||
| 18 | pub(crate) mod model; | ||
| 19 | pub(crate) mod properties; | ||
| 20 | pub(crate) mod table; | ||
| 21 | #[cfg(test)] | ||
| 22 | mod tests; | ||
| 23 | |||
| 24 | pub use lower::{lower, lower_page}; | ||
| 25 | |||
| 26 | /// Property identifiers with their encoded values. | ||
| 27 | pub(crate) type Values = Vec<(u32, Vec<u8>)>; | ||
| 28 | |||
| 29 | /// One user action, applied whole or not at all. | ||
| 30 | #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] | ||
| 31 | pub struct Edit { | ||
| 32 | /// FILETIME when the action happened; the modification time of what it changes. | ||
| 33 | pub at: u64, | ||
| 34 | pub ops: Vec<Op>, | ||
| 35 | } | ||
| 36 | |||
| 37 | #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] | ||
| 38 | pub enum Op { | ||
| 39 | Page { space: ExGuid, op: PageOp }, | ||
| 40 | Section(SectionOp), | ||
| 41 | } | ||
| 42 | |||
| 43 | /// A change to the page an object space holds. Targets are stored identities; paragraph | ||
| 44 | /// properties name the paragraph, text edits its text object. | ||
| 45 | #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] | ||
| 46 | pub enum PageOp { | ||
| 47 | /// Replaces a range; inserted text takes the format of the run it lands in, the | ||
| 48 | /// following run at a boundary except at the end. | ||
| 49 | Text { | ||
| 50 | text: ExGuid, | ||
| 51 | range: Range<u32>, | ||
| 52 | with: String, | ||
| 53 | }, | ||
| 54 | /// Sets and clears character formatting over a range; an empty text's `0..0` is its | ||
| 55 | /// insertion format. Cleared properties are inherited again. | ||
| 56 | Format { | ||
| 57 | text: ExGuid, | ||
| 58 | range: Range<u32>, | ||
| 59 | set: Vec<TextAttribute>, | ||
| 60 | clear: Vec<TextProperty>, | ||
| 61 | }, | ||
| 62 | /// Makes the range a hyperlink to `target`, its field code stored hidden before the | ||
| 63 | /// label, or with `None` removes the link around it, leaving the label as plain text. | ||
| 64 | Link { | ||
| 65 | text: ExGuid, | ||
| 66 | range: Range<u32>, | ||
| 67 | target: Option<String>, | ||
| 68 | }, | ||
| 69 | /// Rewrites an equation's linear text and spans whole. | ||
| 70 | Equation { text: ExGuid, math: Paragraph }, | ||
| 71 | /// Inserts paragraphs before a direct child of `container`, or last. Children follow | ||
| 72 | /// their parents in `paragraphs`; levels are absolute. Text keeps its spans' formats; | ||
| 73 | /// lists, tags, styles and collapse state are set by their own ops. | ||
| 74 | Insert { | ||
| 75 | container: ExGuid, | ||
| 76 | before: Option<ExGuid>, | ||
| 77 | paragraphs: Vec<PageParagraph>, | ||
| 78 | }, | ||
| 79 | /// Enter at `at`: the text left of it stays, the rest moves to a new paragraph `paragraph` | ||
| 80 | /// with text object `right` and a copy of each list node under `lists`, in order. | ||
| 81 | Split { | ||
| 82 | text: ExGuid, | ||
| 83 | at: u32, | ||
| 84 | paragraph: ExGuid, | ||
| 85 | right: ExGuid, | ||
| 86 | lists: Vec<ExGuid>, | ||
| 87 | }, | ||
| 88 | /// Appends the right text to the left one and removes the right paragraph; an empty | ||
| 89 | /// left text is replaced by the right text object. | ||
| 90 | Join { left: ExGuid, right: ExGuid }, | ||
| 91 | /// Moves a subtree before a direct child of `parent`, or last; `None` names the page, | ||
| 92 | /// whose children are outlines, pictures and ink. A paragraph keeps its level where it | ||
| 93 | /// lies deeper than its new parent. | ||
| 94 | Move { | ||
| 95 | object: ExGuid, | ||
| 96 | parent: Option<ExGuid>, | ||
| 97 | before: Option<ExGuid>, | ||
| 98 | }, | ||
| 99 | /// Removes a subtree; an emptied table cell receives an empty paragraph. | ||
| 100 | Delete { object: ExGuid }, | ||
| 101 | /// Sets a paragraph's absolute outline level, regrouping its container. | ||
| 102 | Level { paragraph: ExGuid, level: u32 }, | ||
| 103 | /// Outline position and width, or a paragraph's saved expansion state. | ||
| 104 | Outline { object: ExGuid, edit: OutlineEdit }, | ||
| 105 | /// Paragraph formatting stored on a paragraph's text; `None` leaves a value as it is. | ||
| 106 | Paragraph { | ||
| 107 | paragraph: ExGuid, | ||
| 108 | alignment: Option<u8>, | ||
| 109 | rtl: Option<bool>, | ||
| 110 | space_before: Option<f32>, | ||
| 111 | space_after: Option<f32>, | ||
| 112 | line_spacing: Option<f32>, | ||
| 113 | language: Option<u32>, | ||
| 114 | }, | ||
| 115 | /// References a paragraph style, created from its definition where the page lacks it. | ||
| 116 | Style { | ||
| 117 | paragraph: ExGuid, | ||
| 118 | style: ExGuid, | ||
| 119 | definition: Definition, | ||
| 120 | }, | ||
| 121 | /// Replaces a paragraph's list nodes; a node another paragraph owns is copied. | ||
| 122 | List { | ||
| 123 | paragraph: ExGuid, | ||
| 124 | lists: Vec<(ExGuid, Definition)>, | ||
| 125 | }, | ||
| 126 | /// Replaces the note tags of a paragraph, its text or a table; tag definitions the page | ||
| 127 | /// lacks are created from `definitions`. | ||
| 128 | Tags { | ||
| 129 | target: ExGuid, | ||
| 130 | tags: Vec<Tag>, | ||
| 131 | definitions: Vec<(ExGuid, Definition)>, | ||
| 132 | }, | ||
| 133 | /// Adds an outline with its paragraphs, a picture with its payload, or ink, before a | ||
| 134 | /// page child or on top. | ||
| 135 | Add { | ||
| 136 | object: PageObject, | ||
| 137 | before: Option<ExGuid>, | ||
| 138 | }, | ||
| 139 | /// A stored picture's position, displayed size and description. | ||
| 140 | Picture { | ||
| 141 | picture: ExGuid, | ||
| 142 | layout: Layout, | ||
| 143 | alt: Option<String>, | ||
| 144 | }, | ||
| 145 | /// A stored attachment's shown name, recorded source path and icon size. | ||
| 146 | Attachment { | ||
| 147 | attachment: ExGuid, | ||
| 148 | filename: String, | ||
| 149 | source_path: Option<String>, | ||
| 150 | size: Option<[f32; 2]>, | ||
| 151 | }, | ||
| 152 | /// Adds and erases whole strokes of stored ink. | ||
| 153 | Strokes { | ||
| 154 | ink: ExGuid, | ||
| 155 | add: Vec<InkStroke>, | ||
| 156 | remove: Vec<ExGuid>, | ||
| 157 | }, | ||
| 158 | Table { table: ExGuid, edit: TableEdit }, | ||
| 159 | } | ||
| 160 | |||
| 161 | #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] | ||
| 162 | pub enum TableEdit { | ||
| 163 | /// New rows before a row, or last, each with one cell per column. | ||
| 164 | Rows { | ||
| 165 | before: Option<ExGuid>, | ||
| 166 | rows: Vec<TableRow>, | ||
| 167 | }, | ||
| 168 | /// A new column at `at`: one cell per row, in row order. | ||
| 169 | Column { | ||
| 170 | at: u32, | ||
| 171 | width: f32, | ||
| 172 | cells: Vec<TableCell>, | ||
| 173 | }, | ||
| 174 | DeleteRow(ExGuid), | ||
| 175 | DeleteColumn(u32), | ||
| 176 | /// Every column's width and lock. | ||
| 177 | Columns(Vec<TableColumn>), | ||
| 178 | Borders(bool), | ||
| 179 | /// A cell's shading (`None` clears it) and indentation table. | ||
| 180 | Cell { | ||
| 181 | cell: ExGuid, | ||
| 182 | shading: Option<u32>, | ||
| 183 | indents: Vec<f32>, | ||
| 184 | }, | ||
| 185 | } | ||
| 186 | |||
| 187 | #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] | ||
| 188 | pub enum SectionOp { | ||
| 189 | Create(PageCreation), | ||
| 190 | /// A page created with `creation` holding `page`'s content under the identities it | ||
| 191 | /// carries (`Page::copy` gives fresh ones); the created title stays. | ||
| 192 | Import { creation: PageCreation, page: Page }, | ||
| 193 | /// Page moves and indentation, in order. | ||
| 194 | Pages(Vec<PageEdit>), | ||
| 195 | /// Removes pages permanently; their revisions stay stored. | ||
| 196 | Delete(Vec<ExGuid>), | ||
| 197 | } | ||
| 198 | |||
| 199 | /// A character property that `PageOp::Format` can clear so the text inherits it. | ||
| 200 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] | ||
| 201 | pub enum TextProperty { | ||
| 202 | Bold, | ||
| 203 | Italic, | ||
| 204 | Underline, | ||
| 205 | Strike, | ||
| 206 | Superscript, | ||
| 207 | Subscript, | ||
| 208 | Hidden, | ||
| 209 | Hyperlink, | ||
| 210 | HyperlinkLabel, | ||
| 211 | Math, | ||
| 212 | Font, | ||
| 213 | FontSize, | ||
| 214 | Color, | ||
| 215 | Highlight, | ||
| 216 | } | ||
| 217 | |||
| 218 | impl TextProperty { | ||
| 219 | pub(crate) fn id(self) -> u32 { | ||
| 220 | match self { | ||
| 221 | Self::Bold => 0x08001c04, | ||
| 222 | Self::Italic => 0x08001c05, | ||
| 223 | Self::Underline => 0x08001c06, | ||
| 224 | Self::Strike => 0x08001c07, | ||
| 225 | Self::Superscript => 0x08001c08, | ||
| 226 | Self::Subscript => 0x08001c09, | ||
| 227 | Self::Hidden => 0x08001e16, | ||
| 228 | Self::Hyperlink => 0x08001e14, | ||
| 229 | Self::HyperlinkLabel => 0x08001e19, | ||
| 230 | Self::Math => 0x08003401, | ||
| 231 | Self::Font => 0x1c001c0a, | ||
| 232 | Self::FontSize => 0x10001c0b, | ||
| 233 | Self::Color => 0x14001c0c, | ||
| 234 | Self::Highlight => 0x14001c0d, | ||
| 235 | } | ||
| 236 | } | ||
| 237 | } | ||
| 238 | |||
| 239 | /// Why `Section::apply` refused an edit; the section is as it was before it. | ||
| 240 | #[derive(Clone, Debug, PartialEq)] | ||
| 241 | pub enum OpError { | ||
| 242 | /// An object an op names is not reachable in its space's active revision. | ||
| 243 | TargetUnavailable(ExGuid), | ||
| 244 | /// A new object's identity is already reachable. | ||
| 245 | DuplicateIdentity(ExGuid), | ||
| 246 | /// An anchor is no direct child of its container, or a move would make a cycle. | ||
| 247 | StructureChanged(&'static str), | ||
| 248 | /// The writers cannot make this change to this content. | ||
| 249 | Unsupported(&'static str), | ||
| 250 | /// Writing failed after the section began changing; it must be reopened. | ||
| 251 | Failed(crate::Error), | ||
| 252 | } | ||
| 253 | |||
| 254 | impl std::fmt::Display for OpError { | ||
| 255 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
| 256 | match self { | ||
| 257 | Self::TargetUnavailable(id) => write!(f, "{id} is not on the page"), | ||
| 258 | Self::DuplicateIdentity(id) => write!(f, "{id} already exists"), | ||
| 259 | Self::StructureChanged(message) | Self::Unsupported(message) => f.write_str(message), | ||
| 260 | Self::Failed(error) => write!(f, "{error}"), | ||
| 261 | } | ||
| 262 | } | ||
| 263 | } | ||
| 264 | |||
| 265 | impl std::error::Error for OpError {} | ||
crates/onestore/src/op/model.rs created+1245| ... | @@ -0,0 +1,1245 @@ | ||
| 1 | //! Ops interpreted on a page model: what reading the page back after `Section::apply` | ||
| 2 | //! yields, for the lowering's prediction and as the tests' oracle. | ||
| 3 | |||
| 4 | use super::{PageOp, TableEdit, TextProperty, content::NATIVE_INDENTS, lower::format_in}; | ||
| 5 | use crate::{ | ||
| 6 | Error, ExGuid, OutlineEdit, TextAttribute, | ||
| 7 | document::{Format, Kind}, | ||
| 8 | page::{ | ||
| 9 | Outline, Page, PageObject, PageParagraph, Paragraph, ParagraphContent, Table, TextObject, | ||
| 10 | }, | ||
| 11 | }; | ||
| 12 | use std::collections::BTreeSet; | ||
| 13 | |||
| 14 | fn invalid(message: &'static str) -> Error { | ||
| 15 | Error { offset: 0, message } | ||
| 16 | } | ||
| 17 | |||
| 18 | /// A length in points as the store keeps it, in half inches. | ||
| 19 | fn stored(points: f32) -> f32 { | ||
| 20 | points / 36.0 * 36.0 | ||
| 21 | } | ||
| 22 | |||
| 23 | /// Where a flat paragraph list lies: a page object, a title outline, then the table cells | ||
| 24 | /// on the way down, each as (paragraph index, row, cell). | ||
| 25 | #[derive(Clone, Debug)] | ||
| 26 | pub(crate) struct Path { | ||
| 27 | object: usize, | ||
| 28 | title_outline: Option<usize>, | ||
| 29 | cells: Vec<(usize, usize, usize)>, | ||
| 30 | } | ||
| 31 | |||
| 32 | /// The lists of a page, each with its path and owner (outline or cell identity). | ||
| 33 | pub(crate) fn lists(page: &Page) -> Vec<(Path, ExGuid, &Vec<PageParagraph>)> { | ||
| 34 | fn cells<'p>( | ||
| 35 | path: &Path, | ||
| 36 | list: &'p [PageParagraph], | ||
| 37 | out: &mut Vec<(Path, ExGuid, &'p Vec<PageParagraph>)>, | ||
| 38 | ) { | ||
| 39 | for (index, paragraph) in list.iter().enumerate() { | ||
| 40 | if let ParagraphContent::Table(table) = &paragraph.content { | ||
| 41 | for (r, row) in table.rows.iter().enumerate() { | ||
| 42 | for (c, cell) in row.cells.iter().enumerate() { | ||
| 43 | let mut path = path.clone(); | ||
| 44 | path.cells.push((index, r, c)); | ||
| 45 | out.push((path.clone(), cell.id, &cell.paragraphs)); | ||
| 46 | cells(&path, &cell.paragraphs, out); | ||
| 47 | } | ||
| 48 | } | ||
| 49 | } | ||
| 50 | } | ||
| 51 | } | ||
| 52 | let mut out = Vec::new(); | ||
| 53 | for (object, value) in page.objects.iter().enumerate() { | ||
| 54 | let outlines: Vec<(Option<usize>, &Outline)> = match value { | ||
| 55 | PageObject::Outline(outline) => vec![(None, outline)], | ||
| 56 | PageObject::Title(title) => title.outlines.iter().enumerate().map(|(i, o)| (Some(i), o)).collect(), | ||
| 57 | _ => Vec::new(), | ||
| 58 | }; | ||
| 59 | for (title_outline, outline) in outlines { | ||
| 60 | let path = Path { | ||
| 61 | object, | ||
| 62 | title_outline, | ||
| 63 | cells: Vec::new(), | ||
| 64 | }; | ||
| 65 | out.push((path.clone(), outline.id, &outline.paragraphs)); | ||
| 66 | cells(&path, &outline.paragraphs, &mut out); | ||
| 67 | } | ||
| 68 | } | ||
| 69 | out | ||
| 70 | } | ||
| 71 | |||
| 72 | pub(crate) fn list_at<'p>(page: &'p mut Page, path: &Path) -> &'p mut Vec<PageParagraph> { | ||
| 73 | let outline = match (&mut page.objects[path.object], path.title_outline) { | ||
| 74 | (PageObject::Outline(outline), None) => outline, | ||
| 75 | (PageObject::Title(title), Some(i)) => &mut title.outlines[i], | ||
| 76 | _ => unreachable!(), | ||
| 77 | }; | ||
| 78 | let mut list = &mut outline.paragraphs; | ||
| 79 | for (index, r, c) in &path.cells { | ||
| 80 | let ParagraphContent::Table(table) = &mut list[*index].content else { | ||
| 81 | unreachable!() | ||
| 82 | }; | ||
| 83 | list = &mut table.rows[*r].cells[*c].paragraphs; | ||
| 84 | } | ||
| 85 | list | ||
| 86 | } | ||
| 87 | |||
| 88 | /// The list holding paragraph `id`, and its index there. | ||
| 89 | fn find(page: &Page, id: ExGuid) -> Option<(Path, usize)> { | ||
| 90 | lists(page).into_iter().find_map(|(path, _, list)| { | ||
| 91 | list.iter().position(|p| p.id == id).map(|index| (path, index)) | ||
| 92 | }) | ||
| 93 | } | ||
| 94 | |||
| 95 | /// The list a container's children lie in: its own for an outline or cell, the one | ||
| 96 | /// holding it for a paragraph. | ||
| 97 | fn container(page: &Page, id: ExGuid) -> Option<(Path, Option<usize>)> { | ||
| 98 | lists(page) | ||
| 99 | .into_iter() | ||
| 100 | .find_map(|(path, owner, list)| { | ||
| 101 | if owner == id { | ||
| 102 | Some((path, None)) | ||
| 103 | } else { | ||
| 104 | list.iter() | ||
| 105 | .position(|p| p.id == id) | ||
| 106 | .map(|index| (path, Some(index))) | ||
| 107 | } | ||
| 108 | }) | ||
| 109 | } | ||
| 110 | |||
| 111 | /// The entries of `list` in the subtree of the paragraph at `index`. | ||
| 112 | fn subtree(list: &[PageParagraph], index: usize) -> std::ops::Range<usize> { | ||
| 113 | let mut members = BTreeSet::from([list[index].id]); | ||
| 114 | let mut end = index + 1; | ||
| 115 | while end < list.len() && list[end].parent.is_some_and(|p| members.contains(&p)) { | ||
| 116 | members.insert(list[end].id); | ||
| 117 | end += 1; | ||
| 118 | } | ||
| 119 | index..end | ||
| 120 | } | ||
| 121 | |||
| 122 | pub(crate) fn paragraph(page: &Page, id: ExGuid) -> Option<&PageParagraph> { | ||
| 123 | lists(page) | ||
| 124 | .into_iter() | ||
| 125 | .find_map(|(_, _, list)| list.iter().find(|p| p.id == id)) | ||
| 126 | } | ||
| 127 | |||
| 128 | pub(crate) fn outline(page: &Page, id: ExGuid) -> Option<&Outline> { | ||
| 129 | page.objects.iter().find_map(|object| match object { | ||
| 130 | PageObject::Outline(outline) if outline.id == id => Some(outline), | ||
| 131 | _ => None, | ||
| 132 | }) | ||
| 133 | } | ||
| 134 | |||
| 135 | pub(crate) fn table(page: &Page, id: ExGuid) -> Option<&Table> { | ||
| 136 | lists(page).into_iter().find_map(|(_, _, list)| { | ||
| 137 | list.iter().find_map(|p| match &p.content { | ||
| 138 | ParagraphContent::Table(table) if table.id == id => Some(table), | ||
| 139 | _ => None, | ||
| 140 | }) | ||
| 141 | }) | ||
| 142 | } | ||
| 143 | |||
| 144 | /// Whether a paragraph of the page lists `list` among its list nodes. | ||
| 145 | pub(crate) fn owns_list(page: &Page, list: ExGuid) -> bool { | ||
| 146 | lists(page) | ||
| 147 | .into_iter() | ||
| 148 | .any(|(_, _, paragraphs)| paragraphs.iter().any(|p| p.lists.contains(&list))) | ||
| 149 | } | ||
| 150 | |||
| 151 | /// Identities of paragraphs and what they hold, tables included. | ||
| 152 | pub(crate) fn identities(paragraphs: &[PageParagraph]) -> Vec<ExGuid> { | ||
| 153 | let mut out = Vec::new(); | ||
| 154 | for paragraph in paragraphs { | ||
| 155 | out.push(paragraph.id); | ||
| 156 | match &paragraph.content { | ||
| 157 | ParagraphContent::Text(text) => out.push(text.id), | ||
| 158 | ParagraphContent::Table(table) => { | ||
| 159 | out.push(table.id); | ||
| 160 | for row in &table.rows { | ||
| 161 | out.push(row.id); | ||
| 162 | for cell in &row.cells { | ||
| 163 | out.push(cell.id); | ||
| 164 | out.extend(identities(&cell.paragraphs)); | ||
| 165 | } | ||
| 166 | } | ||
| 167 | } | ||
| 168 | ParagraphContent::Image(image) => out.push(image.id), | ||
| 169 | ParagraphContent::Attachment(attachment) => out.push(attachment.id), | ||
| 170 | ParagraphContent::Ink(ink) => { | ||
| 171 | out.push(ink.id); | ||
| 172 | out.extend(ink.strokes.iter().map(|s| s.id)); | ||
| 173 | } | ||
| 174 | ParagraphContent::Unsupported(unsupported) => out.push(unsupported.id), | ||
| 175 | } | ||
| 176 | out.extend(paragraph.lists.iter().copied()); | ||
| 177 | } | ||
| 178 | out | ||
| 179 | } | ||
| 180 | |||
| 181 | /// Whether the page holds `id` as an object of the model. | ||
| 182 | pub(crate) fn holds(page: &Page, id: ExGuid) -> bool { | ||
| 183 | page.objects.iter().any(|object| { | ||
| 184 | object.id() == id | ||
| 185 | || match object { | ||
| 186 | PageObject::Outline(outline) => identities(&outline.paragraphs).contains(&id), | ||
| 187 | PageObject::Title(title) => title | ||
| 188 | .outlines | ||
| 189 | .iter() | ||
| 190 | .any(|o| o.id == id || identities(&o.paragraphs).contains(&id)), | ||
| 191 | PageObject::Ink(ink) => ink.strokes.iter().any(|s| s.id == id), | ||
| 192 | PageObject::Image(_) | PageObject::Unsupported(_) => false, | ||
| 193 | } | ||
| 194 | }) | ||
| 195 | } | ||
| 196 | |||
| 197 | /// The paragraph holding text object `text`: its list and index. | ||
| 198 | fn text_holder(page: &Page, text: ExGuid) -> Option<(Path, usize)> { | ||
| 199 | lists(page).into_iter().find_map(|(path, _, list)| { | ||
| 200 | list.iter() | ||
| 201 | .position(|p| p.text().is_some_and(|t| t.id == text)) | ||
| 202 | .map(|index| (path, index)) | ||
| 203 | }) | ||
| 204 | } | ||
| 205 | |||
| 206 | fn text_mut(page: &mut Page, text: ExGuid) -> Result<&mut TextObject, Error> { | ||
| 207 | let (path, index) = text_holder(page, text).ok_or_else(|| invalid("Text is not on the page"))?; | ||
| 208 | Ok(list_at(page, &path)[index].text_mut().unwrap()) | ||
| 209 | } | ||
| 210 | |||
| 211 | fn paragraph_mut(page: &mut Page, id: ExGuid) -> Result<&mut PageParagraph, Error> { | ||
| 212 | let (path, index) = find(page, id).ok_or_else(|| invalid("Paragraph is not on the page"))?; | ||
| 213 | Ok(&mut list_at(page, &path)[index]) | ||
| 214 | } | ||
| 215 | |||
| 216 | fn table_mut(page: &mut Page, id: ExGuid) -> Result<&mut Table, Error> { | ||
| 217 | let (path, index) = lists(page) | ||
| 218 | .into_iter() | ||
| 219 | .find_map(|(path, _, list)| { | ||
| 220 | list.iter() | ||
| 221 | .position(|p| matches!(&p.content, ParagraphContent::Table(t) if t.id == id)) | ||
| 222 | .map(|index| (path, index)) | ||
| 223 | }) | ||
| 224 | .ok_or_else(|| invalid("Table is not on the page"))?; | ||
| 225 | let ParagraphContent::Table(table) = &mut list_at(page, &path)[index].content else { | ||
| 226 | unreachable!() | ||
| 227 | }; | ||
| 228 | Ok(table) | ||
| 229 | } | ||
| 230 | |||
| 231 | /// `text` with a range replaced as the text writer does: inserted text takes the format of | ||
| 232 | /// the run it lands in, and emptied text keeps its last run's. | ||
| 233 | pub(crate) fn replace_text( | ||
| 234 | text: &Paragraph, | ||
| 235 | range: std::ops::Range<u32>, | ||
| 236 | with: &str, | ||
| 237 | ) -> Result<Paragraph, Error> { | ||
| 238 | let length = text.utf16_offset(text.text().len())?; | ||
| 239 | if range.start > range.end || range.end > length { | ||
| 240 | return Err(invalid("The edit range exceeds the text")); | ||
| 241 | } | ||
| 242 | // Replacing text with itself leaves its formatting as it is. | ||
| 243 | if text.text()[text.byte_offset(range.start)?..text.byte_offset(range.end)?] == *with { | ||
| 244 | return Ok(text.clone()); | ||
| 245 | } | ||
| 246 | let format = format_in(text, range.start)?.clone(); | ||
| 247 | let removed = range.end - range.start; | ||
| 248 | if length - removed == 0 && with.is_empty() { | ||
| 249 | return Ok(Paragraph::new(String::new(), text.spans().last().unwrap().format.clone())); | ||
| 250 | } | ||
| 251 | let mut result = text.clone(); | ||
| 252 | result.apply(crate::page::text::Edit { | ||
| 253 | range, | ||
| 254 | replacement: Paragraph::new(with.to_owned(), format), | ||
| 255 | })?; | ||
| 256 | Ok(result) | ||
| 257 | } | ||
| 258 | |||
| 259 | /// `text` with every span taking `from`'s paragraph formatting, which the text object | ||
| 260 | /// stores for all its runs. | ||
| 261 | fn paragraph_formatted(text: &Paragraph, from: &Format) -> Paragraph { | ||
| 262 | let mut previous = 0; | ||
| 263 | Paragraph::from_runs(text.spans().iter().map(|span| { | ||
| 264 | let mut format = span.format.clone(); | ||
| 265 | format.alignment = from.alignment; | ||
| 266 | format.rtl = from.rtl; | ||
| 267 | format.space_before = from.space_before; | ||
| 268 | format.space_after = from.space_after; | ||
| 269 | format.line_spacing = from.line_spacing; | ||
| 270 | let run = (text.text()[previous..span.end].to_owned(), format); | ||
| 271 | previous = span.end; | ||
| 272 | run | ||
| 273 | })) | ||
| 274 | } | ||
| 275 | |||
| 276 | fn set(format: &mut Format, attribute: &TextAttribute) { | ||
| 277 | let color = |value: &Option<[u8; 3]>| { | ||
| 278 | Some(value.map_or(0xff000000, |[r, g, b]| u32::from_le_bytes([r, g, b, 0]))) | ||
| 279 | }; | ||
| 280 | match attribute { | ||
| 281 | TextAttribute::Bold(v) => format.bold = Some(*v), | ||
| 282 | TextAttribute::Italic(v) => format.italic = Some(*v), | ||
| 283 | TextAttribute::Underline(v) => format.underline = Some(*v), | ||
| 284 | TextAttribute::Strike(v) => format.strike = Some(*v), | ||
| 285 | TextAttribute::Superscript(v) => { | ||
| 286 | format.superscript = Some(*v); | ||
| 287 | if *v { | ||
| 288 | format.subscript = Some(false); | ||
| 289 | } | ||
| 290 | } | ||
| 291 | TextAttribute::Subscript(v) => { | ||
| 292 | format.subscript = Some(*v); | ||
| 293 | if *v { | ||
| 294 | format.superscript = Some(false); | ||
| 295 | } | ||
| 296 | } | ||
| 297 | TextAttribute::Font(font) => format.font = Some(font.clone()), | ||
| 298 | TextAttribute::FontSize(size) => format.font_size = Some(*size), | ||
| 299 | TextAttribute::Color(value) => format.color = color(value), | ||
| 300 | TextAttribute::Highlight(value) => format.highlight = color(value), | ||
| 301 | TextAttribute::Language(language) => format.language = Some(*language), | ||
| 302 | TextAttribute::Hidden(v) => format.hidden = Some(*v), | ||
| 303 | TextAttribute::Hyperlink(v) => format.hyperlink = Some(*v), | ||
| 304 | TextAttribute::HyperlinkLabel(v) => format.hyperlink_label = Some(*v), | ||
| 305 | TextAttribute::Math(v) => format.math = Some(*v), | ||
| 306 | } | ||
| 307 | } | ||
| 308 | |||
| 309 | fn clear(format: &mut Format, property: TextProperty) { | ||
| 310 | match property { | ||
| 311 | TextProperty::Bold => format.bold = None, | ||
| 312 | TextProperty::Italic => format.italic = None, | ||
| 313 | TextProperty::Underline => format.underline = None, | ||
| 314 | TextProperty::Strike => format.strike = None, | ||
| 315 | TextProperty::Superscript => format.superscript = None, | ||
| 316 | TextProperty::Subscript => format.subscript = None, | ||
| 317 | TextProperty::Hidden => format.hidden = None, | ||
| 318 | TextProperty::Hyperlink => format.hyperlink = None, | ||
| 319 | TextProperty::HyperlinkLabel => format.hyperlink_label = None, | ||
| 320 | TextProperty::Math => format.math = None, | ||
| 321 | TextProperty::Font => format.font = None, | ||
| 322 | TextProperty::FontSize => format.font_size = None, | ||
| 323 | TextProperty::Color => format.color = None, | ||
| 324 | TextProperty::Highlight => format.highlight = None, | ||
| 325 | } | ||
| 326 | } | ||
| 327 | |||
| 328 | /// `text` with a range reformatted as the format writer does; a reformatted run states its | ||
| 329 | /// language. | ||
| 330 | pub(crate) fn format_text( | ||
| 331 | text: &Paragraph, | ||
| 332 | range: std::ops::Range<u32>, | ||
| 333 | attributes: &[TextAttribute], | ||
| 334 | cleared: &[TextProperty], | ||
| 335 | style: &Format, | ||
| 336 | ) -> Result<Paragraph, Error> { | ||
| 337 | let apply = |format: &Format| { | ||
| 338 | let mut format = format.clone(); | ||
| 339 | for attribute in attributes { | ||
| 340 | set(&mut format, attribute); | ||
| 341 | } | ||
| 342 | for property in cleared { | ||
| 343 | clear(&mut format, *property); | ||
| 344 | } | ||
| 345 | // A cleared property shows the paragraph style's value. | ||
| 346 | format = format.inherit(style); | ||
| 347 | if format.language.is_none() { | ||
| 348 | format.language = Some(0x409); | ||
| 349 | } | ||
| 350 | format | ||
| 351 | }; | ||
| 352 | if text.text().is_empty() { | ||
| 353 | return Ok(Paragraph::new(String::new(), apply(&text.spans()[0].format))); | ||
| 354 | } | ||
| 355 | let (start, end) = (text.byte_offset(range.start)?, text.byte_offset(range.end)?); | ||
| 356 | let mut runs = Vec::new(); | ||
| 357 | let mut previous = 0; | ||
| 358 | for span in text.spans() { | ||
| 359 | for (from, to, selected) in [ | ||
| 360 | (previous, start.clamp(previous, span.end), false), | ||
| 361 | (start.clamp(previous, span.end), end.clamp(previous, span.end), true), | ||
| 362 | (end.clamp(previous, span.end), span.end, false), | ||
| 363 | ] { | ||
| 364 | if from < to { | ||
| 365 | let format = if selected { apply(&span.format) } else { span.format.clone() }; | ||
| 366 | runs.push((text.text()[from..to].to_owned(), format)); | ||
| 367 | } | ||
| 368 | } | ||
| 369 | previous = span.end; | ||
| 370 | } | ||
| 371 | Ok(Paragraph::from_runs(runs)) | ||
| 372 | } | ||
| 373 | |||
| 374 | /// A text as `Insert` stores it: an unset language is the writer's insertion language, and | ||
| 375 | /// paragraph formatting is stored only where every span shares a value other than the default. | ||
| 376 | fn inserted(text: &Paragraph) -> Paragraph { | ||
| 377 | let spans = text.spans(); | ||
| 378 | macro_rules! shared { | ||
| 379 | ($field:ident, $type:ty, $store:expr) => {{ | ||
| 380 | let first = spans[0].format.$field.unwrap_or_default(); | ||
| 381 | (spans.iter().all(|s| s.format.$field.unwrap_or_default() == first) | ||
| 382 | && first != <$type>::default()) | ||
| 383 | .then(|| $store(first)) | ||
| 384 | }}; | ||
| 385 | } | ||
| 386 | let alignment = shared!(alignment, u8, |v| v); | ||
| 387 | let rtl = shared!(rtl, bool, |v| v); | ||
| 388 | let space_before = shared!(space_before, f32, stored); | ||
| 389 | let space_after = shared!(space_after, f32, stored); | ||
| 390 | let line_spacing = shared!(line_spacing, f32, stored); | ||
| 391 | let mut previous = 0; | ||
| 392 | Paragraph::from_runs(spans.iter().map(|span| { | ||
| 393 | let mut format = span.format.clone(); | ||
| 394 | format.language.get_or_insert(0x409); | ||
| 395 | format.alignment = alignment; | ||
| 396 | format.rtl = rtl; | ||
| 397 | format.space_before = space_before; | ||
| 398 | format.space_after = space_after; | ||
| 399 | format.line_spacing = line_spacing; | ||
| 400 | let run = (text.text()[previous..span.end].to_owned(), format); | ||
| 401 | previous = span.end; | ||
| 402 | run | ||
| 403 | })) | ||
| 404 | } | ||
| 405 | |||
| 406 | /// A stroke as the store keeps it: points on the HIMETRIC grid, pen size in HIMETRIC. | ||
| 407 | fn stroked(stroke: &crate::page::InkStroke) -> crate::page::InkStroke { | ||
| 408 | let himetric = |points: f32| points * 2540.0 / 72.0 * 72.0 / 2540.0; | ||
| 409 | crate::page::InkStroke { | ||
| 410 | points: stroke | ||
| 411 | .points | ||
| 412 | .iter() | ||
| 413 | .map(|[x, y]| [crate::page::ink::snap(*x), crate::page::ink::snap(*y)]) | ||
| 414 | .collect(), | ||
| 415 | width: himetric(stroke.width), | ||
| 416 | height: himetric(stroke.height), | ||
| 417 | ..stroke.clone() | ||
| 418 | } | ||
| 419 | } | ||
| 420 | |||
| 421 | fn inked(ink: &crate::page::Ink) -> crate::page::Ink { | ||
| 422 | crate::page::Ink { | ||
| 423 | strokes: ink.strokes.iter().map(stroked).collect(), | ||
| 424 | ..ink.clone() | ||
| 425 | } | ||
| 426 | } | ||
| 427 | |||
| 428 | /// Paragraphs as `Insert` stores them. | ||
| 429 | fn insertion(paragraphs: &[PageParagraph]) -> Vec<PageParagraph> { | ||
| 430 | paragraphs | ||
| 431 | .iter() | ||
| 432 | .map(|paragraph| { | ||
| 433 | let mut paragraph = paragraph.clone(); | ||
| 434 | match &mut paragraph.content { | ||
| 435 | ParagraphContent::Text(text) if !crate::page::Math::is_equation(&text.text) => { | ||
| 436 | text.text = inserted(&text.text); | ||
| 437 | } | ||
| 438 | // An equation is written whole, without paragraph formatting. | ||
| 439 | ParagraphContent::Text(text) => { | ||
| 440 | text.text = paragraph_formatted(&text.text, &Format::default()); | ||
| 441 | } | ||
| 442 | ParagraphContent::Table(table) => { | ||
| 443 | for cell in table.rows.iter_mut().flat_map(|row| &mut row.cells) { | ||
| 444 | if cell.indents.is_empty() { | ||
| 445 | cell.indents = NATIVE_INDENTS.to_vec(); | ||
| 446 | } | ||
| 447 | cell.paragraphs = insertion(&cell.paragraphs); | ||
| 448 | } | ||
| 449 | } | ||
| 450 | ParagraphContent::Ink(ink) => *ink = inked(ink), | ||
| 451 | _ => {} | ||
| 452 | } | ||
| 453 | paragraph | ||
| 454 | }) | ||
| 455 | .collect() | ||
| 456 | } | ||
| 457 | |||
| 458 | /// Inserts `paragraphs` (roots then descendants) into `container` before `before`. | ||
| 459 | fn insert( | ||
| 460 | page: &mut Page, | ||
| 461 | container: ExGuid, | ||
| 462 | before: Option<ExGuid>, | ||
| 463 | paragraphs: Vec<PageParagraph>, | ||
| 464 | ) -> Result<(), Error> { | ||
| 465 | let (path, owner) = | ||
| 466 | self::container(page, container).ok_or_else(|| invalid("The container is not on the page"))?; | ||
| 467 | let list = list_at(page, &path); | ||
| 468 | let at = match before { | ||
| 469 | Some(before) => list | ||
| 470 | .iter() | ||
| 471 | .position(|p| p.id == before) | ||
| 472 | .ok_or_else(|| invalid("The anchor is not on the page"))?, | ||
| 473 | None => match owner { | ||
| 474 | Some(index) => subtree(list, index).end, | ||
| 475 | None => list.len(), | ||
| 476 | }, | ||
| 477 | }; | ||
| 478 | list.splice(at..at, paragraphs); | ||
| 479 | Ok(()) | ||
| 480 | } | ||
| 481 | |||
| 482 | /// The absolute level of the children of `container`. | ||
| 483 | fn child_level(page: &Page, container: ExGuid) -> u32 { | ||
| 484 | paragraph(page, container).map_or(0, |p| p.level) | ||
| 485 | } | ||
| 486 | |||
| 487 | /// Shifts the levels of the subtree at `index` so its root lies at `level`. | ||
| 488 | fn relevel(list: &mut [PageParagraph], index: usize, level: u32) { | ||
| 489 | let range = subtree(list, index); | ||
| 490 | let delta = i64::from(level) - i64::from(list[index].level); | ||
| 491 | for paragraph in &mut list[range] { | ||
| 492 | paragraph.level = (i64::from(paragraph.level) + delta) as u32; | ||
| 493 | } | ||
| 494 | } | ||
| 495 | |||
| 496 | /// The title a page's writers store after changing it: the title text's first line when | ||
| 497 | /// it has one, which only an edit of that text changes, else the first line of body text. | ||
| 498 | fn retitle(page: &mut Page, edited: Option<ExGuid>) { | ||
| 499 | let line = |text: &str| text.trim_start().split('\r').next().unwrap_or_default().to_owned(); | ||
| 500 | match find_title_text(page) { | ||
| 501 | Some(title) => { | ||
| 502 | let text = text_of(page, title); | ||
| 503 | if line(&text).is_empty() { | ||
| 504 | automatic(page); | ||
| 505 | } else if edited == Some(title) { | ||
| 506 | page.title = line(&text); | ||
| 507 | } | ||
| 508 | } | ||
| 509 | None => automatic(page), | ||
| 510 | } | ||
| 511 | } | ||
| 512 | |||
| 513 | fn automatic(page: &mut Page) { | ||
| 514 | let mut roots: Vec<&PageObject> = page | ||
| 515 | .objects | ||
| 516 | .iter() | ||
| 517 | .filter(|object| !matches!(object, PageObject::Title(_))) | ||
| 518 | .collect(); | ||
| 519 | roots.sort_by(|a, b| { | ||
| 520 | let (a, b) = (a.layout(), b.layout()); | ||
| 521 | a.y.unwrap_or(0.0) | ||
| 522 | .total_cmp(&b.y.unwrap_or(0.0)) | ||
| 523 | .then_with(|| a.x.unwrap_or(0.0).total_cmp(&b.x.unwrap_or(0.0))) | ||
| 524 | }); | ||
| 525 | fn first(list: &[PageParagraph]) -> Option<&str> { | ||
| 526 | list.iter().find_map(|paragraph| match &paragraph.content { | ||
| 527 | ParagraphContent::Text(text) => { | ||
| 528 | Some(crate::edit::automatic_title(text.text.text())).filter(|t| !t.is_empty()) | ||
| 529 | } | ||
| 530 | ParagraphContent::Table(table) => table | ||
| 531 | .rows | ||
| 532 | .iter() | ||
| 533 | .flat_map(|row| &row.cells) | ||
| 534 | .find_map(|cell| first(&cell.paragraphs)), | ||
| 535 | _ => None, | ||
| 536 | }) | ||
| 537 | } | ||
| 538 | let title = roots | ||
| 539 | .into_iter() | ||
| 540 | .find_map(|object| match object { | ||
| 541 | PageObject::Outline(outline) => first(&outline.paragraphs), | ||
| 542 | _ => None, | ||
| 543 | }) | ||
| 544 | .unwrap_or_default() | ||
| 545 | .to_owned(); | ||
| 546 | page.title = title; | ||
| 547 | } | ||
| 548 | |||
| 549 | /// Applies `op` to `page` as reading the page back after `Section::apply` would show it. | ||
| 550 | pub(crate) fn apply(page: &mut Page, op: &PageOp) -> Result<(), Error> { | ||
| 551 | interpret(page, op)?; | ||
| 552 | // A page lists the definitions its paragraphs reference. | ||
| 553 | let mut referenced = BTreeSet::new(); | ||
| 554 | for (_, _, list) in lists(page) { | ||
| 555 | for paragraph in list { | ||
| 556 | referenced.extend(paragraph.lists.iter().copied()); | ||
| 557 | referenced.extend(paragraph.style); | ||
| 558 | let tags = paragraph.tags.iter().chain(match &paragraph.content { | ||
| 559 | ParagraphContent::Text(text) => text.tags.as_slice(), | ||
| 560 | ParagraphContent::Table(table) => table.tags.as_slice(), | ||
| 561 | _ => &[], | ||
| 562 | }); | ||
| 563 | referenced.extend(tags.filter_map(|tag| tag.definition)); | ||
| 564 | } | ||
| 565 | } | ||
| 566 | page.definitions.retain(|id, _| referenced.contains(id)); | ||
| 567 | Ok(()) | ||
| 568 | } | ||
| 569 | |||
| 570 | fn interpret(page: &mut Page, op: &PageOp) -> Result<(), Error> { | ||
| 571 | match op { | ||
| 572 | PageOp::Text { text, range, with } => { | ||
| 573 | let object = text_mut(page, *text)?; | ||
| 574 | object.text = replace_text(&object.text, range.clone(), with)?; | ||
| 575 | retitle(page, Some(*text)); | ||
| 576 | } | ||
| 577 | PageOp::Format { | ||
| 578 | text, | ||
| 579 | range, | ||
| 580 | set, | ||
| 581 | clear, | ||
| 582 | } => { | ||
| 583 | let (path, index) = | ||
| 584 | text_holder(page, *text).ok_or_else(|| invalid("Text is not on the page"))?; | ||
| 585 | let style = list_at(page, &path)[index] | ||
| 586 | .style | ||
| 587 | .and_then(|style| page.definitions.get(&style)) | ||
| 588 | .map(|definition| definition.format.clone()) | ||
| 589 | .unwrap_or_default(); | ||
| 590 | let object = text_mut(page, *text)?; | ||
| 591 | object.text = format_text(&object.text, range.clone(), set, clear, &style)?; | ||
| 592 | } | ||
| 593 | PageOp::Link { | ||
| 594 | text, | ||
| 595 | range, | ||
| 596 | target, | ||
| 597 | } => { | ||
| 598 | let current = text_mut(page, *text)?.text.clone(); | ||
| 599 | for op in super::apply::link_ops(&current, *text, range.clone(), target.as_deref())? { | ||
| 600 | interpret(page, &op)?; | ||
| 601 | } | ||
| 602 | } | ||
| 603 | PageOp::Equation { text, math } => { | ||
| 604 | let object = text_mut(page, *text)?; | ||
| 605 | let previous = object.text.spans()[0].format.clone(); | ||
| 606 | object.text = paragraph_formatted(math, &previous); | ||
| 607 | } | ||
| 608 | PageOp::Insert { | ||
| 609 | container, | ||
| 610 | before, | ||
| 611 | paragraphs, | ||
| 612 | } => { | ||
| 613 | insert(page, *container, *before, insertion(paragraphs))?; | ||
| 614 | retitle(page, None); | ||
| 615 | } | ||
| 616 | PageOp::Split { | ||
| 617 | text, | ||
| 618 | at, | ||
| 619 | paragraph, | ||
| 620 | right, | ||
| 621 | lists, | ||
| 622 | } => { | ||
| 623 | let (path, index) = text_holder(page, *text).ok_or_else(|| invalid("Text is not on the page"))?; | ||
| 624 | let definitions: Vec<_> = { | ||
| 625 | let list = list_at(page, &path); | ||
| 626 | list[index].lists.clone() | ||
| 627 | }; | ||
| 628 | let copies: Vec<_> = definitions | ||
| 629 | .iter() | ||
| 630 | .zip(lists) | ||
| 631 | .map(|(old, new)| { | ||
| 632 | let mut definition = page.definitions.get(old).cloned(); | ||
| 633 | if let Some(crate::page::Definition { | ||
| 634 | kind: Kind::List { restart, .. }, | ||
| 635 | .. | ||
| 636 | }) = &mut definition | ||
| 637 | { | ||
| 638 | *restart = None; | ||
| 639 | } | ||
| 640 | (*new, definition) | ||
| 641 | }) | ||
| 642 | .collect(); | ||
| 643 | for (id, definition) in copies { | ||
| 644 | if let Some(definition) = definition { | ||
| 645 | page.definitions.insert(id, definition); | ||
| 646 | } | ||
| 647 | } | ||
| 648 | let list = list_at(page, &path); | ||
| 649 | let left = &mut list[index]; | ||
| 650 | let source = left.text().unwrap().text.clone(); | ||
| 651 | let length = source.utf16_offset(source.text().len())?; | ||
| 652 | let (head, tail) = (source.slice(0..*at)?, source.slice(*at..length)?); | ||
| 653 | let mut new = left.clone(); | ||
| 654 | left.text_mut().unwrap().text = head; | ||
| 655 | new.id = *paragraph; | ||
| 656 | new.lists = lists.clone(); | ||
| 657 | new.media = Default::default(); | ||
| 658 | new.content = ParagraphContent::Text(TextObject { | ||
| 659 | id: *right, | ||
| 660 | date_field: None, | ||
| 661 | text: tail, | ||
| 662 | tags: Vec::new(), | ||
| 663 | }); | ||
| 664 | let left_id = list[index].id; | ||
| 665 | for child in &mut list[index + 1..] { | ||
| 666 | if child.parent == Some(left_id) { | ||
| 667 | child.parent = Some(*paragraph); | ||
| 668 | } | ||
| 669 | } | ||
| 670 | list.insert(index + 1, new); | ||
| 671 | retitle(page, None); | ||
| 672 | } | ||
| 673 | PageOp::Join { left, right } => { | ||
| 674 | let (path, index) = text_holder(page, *right).ok_or_else(|| invalid("Text is not on the page"))?; | ||
| 675 | let (left_path, left_index) = | ||
| 676 | text_holder(page, *left).ok_or_else(|| invalid("Text is not on the page"))?; | ||
| 677 | let list = list_at(page, &path); | ||
| 678 | let removed = list[index].clone(); | ||
| 679 | let right_text = removed.text().unwrap().clone(); | ||
| 680 | let definitions = page.definitions.clone(); | ||
| 681 | let left_list = list_at(page, &left_path); | ||
| 682 | let left_style = left_list[left_index].style; | ||
| 683 | let adopts = left_list[left_index].text().unwrap().text.text().is_empty(); | ||
| 684 | if adopts { | ||
| 685 | // The text object brings its paragraph style and recording link. | ||
| 686 | left_list[left_index].style = removed.style; | ||
| 687 | left_list[left_index].media = removed.media.clone(); | ||
| 688 | } | ||
| 689 | let upper = left_list[left_index].text_mut().unwrap(); | ||
| 690 | if adopts { | ||
| 691 | upper.id = right_text.id; | ||
| 692 | upper.text = right_text.text; | ||
| 693 | } else { | ||
| 694 | // The lower text keeps its look under the upper paragraph's style: a flag or | ||
| 695 | // colour only that style states becomes false or automatic. | ||
| 696 | let differs = left_style != removed.style; | ||
| 697 | let left = left_style | ||
| 698 | .and_then(|style| definitions.get(&style)) | ||
| 699 | .map(|definition| definition.format.clone()) | ||
| 700 | .unwrap_or_default(); | ||
| 701 | let mut previous = 0; | ||
| 702 | let lower = Paragraph::from_runs(right_text.text.spans().iter().map(|span| { | ||
| 703 | let mut format = span.format.clone(); | ||
| 704 | if differs { | ||
| 705 | for (field, base) in [ | ||
| 706 | (&mut format.bold, left.bold), | ||
| 707 | (&mut format.italic, left.italic), | ||
| 708 | (&mut format.underline, left.underline), | ||
| 709 | (&mut format.strike, left.strike), | ||
| 710 | (&mut format.superscript, left.superscript), | ||
| 711 | (&mut format.subscript, left.subscript), | ||
| 712 | (&mut format.hidden, left.hidden), | ||
| 713 | (&mut format.hyperlink, left.hyperlink), | ||
| 714 | (&mut format.hyperlink_label, left.hyperlink_label), | ||
| 715 | (&mut format.math, left.math), | ||
| 716 | ] { | ||
| 717 | if field.is_none() && base.is_some() { | ||
| 718 | *field = Some(false); | ||
| 719 | } | ||
| 720 | } | ||
| 721 | for (field, base) in [ | ||
| 722 | (&mut format.color, left.color), | ||
| 723 | (&mut format.highlight, left.highlight), | ||
| 724 | ] { | ||
| 725 | if field.is_none() && base.is_some() { | ||
| 726 | *field = Some(0xff000000); | ||
| 727 | } | ||
| 728 | } | ||
| 729 | } | ||
| 730 | let run = (right_text.text.text()[previous..span.end].to_owned(), format); | ||
| 731 | previous = span.end; | ||
| 732 | run | ||
| 733 | })); | ||
| 734 | let paragraph = upper.text.spans()[0].format.clone(); | ||
| 735 | upper.text.append(lower)?; | ||
| 736 | upper.text = paragraph_formatted(&upper.text, &paragraph); | ||
| 737 | } | ||
| 738 | let left_id = left_list[left_index].id; | ||
| 739 | let list = list_at(page, &path); | ||
| 740 | // The right paragraph's children join the left paragraph's ancestor beside it, | ||
| 741 | // which is the outline group holding it where it lies deeper. | ||
| 742 | let mut stem = left_id; | ||
| 743 | while let Some(parent) = list.iter().find(|p| p.id == stem).and_then(|p| p.parent) { | ||
| 744 | if Some(parent) == removed.parent { | ||
| 745 | break; | ||
| 746 | } | ||
| 747 | stem = parent; | ||
| 748 | } | ||
| 749 | let range = subtree(list, index); | ||
| 750 | let stem_at = list.iter().position(|p| p.id == stem).unwrap(); | ||
| 751 | let stem_level = list[stem_at].level; | ||
| 752 | if range.len() > 1 && stem_level > removed.level { | ||
| 753 | // The group takes the right paragraph's child level for all its members. | ||
| 754 | let level = list[range.start + 1].level; | ||
| 755 | let mut member = stem_at; | ||
| 756 | loop { | ||
| 757 | relevel(list, member, level); | ||
| 758 | let Some(previous) = list[..member] | ||
| 759 | .iter() | ||
| 760 | .rposition(|p| p.parent == removed.parent) | ||
| 761 | .filter(|at| list[*at].level == stem_level) | ||
| 762 | else { | ||
| 763 | break; | ||
| 764 | }; | ||
| 765 | member = previous; | ||
| 766 | } | ||
| 767 | for child in &mut list[range.start + 1..range.end] { | ||
| 768 | if child.parent == Some(removed.id) { | ||
| 769 | child.parent = removed.parent; | ||
| 770 | } | ||
| 771 | } | ||
| 772 | } else { | ||
| 773 | let delta = i64::from(stem_level) - i64::from(removed.level); | ||
| 774 | for child in &mut list[range.start + 1..range.end] { | ||
| 775 | child.level = (i64::from(child.level) + delta) as u32; | ||
| 776 | if child.parent == Some(removed.id) { | ||
| 777 | child.parent = Some(stem); | ||
| 778 | } | ||
| 779 | } | ||
| 780 | } | ||
| 781 | list.remove(index); | ||
| 782 | retitle(page, None); | ||
| 783 | } | ||
| 784 | PageOp::Move { | ||
| 785 | object, | ||
| 786 | parent, | ||
| 787 | before, | ||
| 788 | } => { | ||
| 789 | let Some(parent) = parent else { | ||
| 790 | let from = page | ||
| 791 | .objects | ||
| 792 | .iter() | ||
| 793 | .position(|o| o.id() == *object) | ||
| 794 | .ok_or_else(|| invalid("The object is not on the page"))?; | ||
| 795 | let moved = page.objects.remove(from); | ||
| 796 | let at = page_position(page, *before)?; | ||
| 797 | page.objects.insert(at, moved); | ||
| 798 | retitle(page, None); | ||
| 799 | return Ok(()); | ||
| 800 | }; | ||
| 801 | let (path, index) = find(page, *object).ok_or_else(|| invalid("The paragraph is not on the page"))?; | ||
| 802 | let list = list_at(page, &path); | ||
| 803 | let mut moved: Vec<PageParagraph> = list.drain(subtree(list, index)).collect(); | ||
| 804 | let into_cell = table_cells(page).contains(parent); | ||
| 805 | let base = child_level(page, *parent); | ||
| 806 | let level = if into_cell { | ||
| 807 | let (path, _) = self::container(page, *parent).unwrap(); | ||
| 808 | list_at(page, &path) | ||
| 809 | .iter() | ||
| 810 | .find(|p| p.parent.is_none()) | ||
| 811 | .map_or(1, |p| p.level) | ||
| 812 | } else if moved[0].level > base { | ||
| 813 | moved[0].level | ||
| 814 | } else { | ||
| 815 | base + 1 | ||
| 816 | }; | ||
| 817 | moved[0].parent = paragraph(page, *parent).map(|_| *parent); | ||
| 818 | relevel(&mut moved, 0, level); | ||
| 819 | insert(page, *parent, *before, moved)?; | ||
| 820 | drop_emptied(page); | ||
| 821 | retitle(page, None); | ||
| 822 | } | ||
| 823 | PageOp::Delete { object } => { | ||
| 824 | if let Some(at) = page.objects.iter().position(|o| o.id() == *object) { | ||
| 825 | page.objects.remove(at); | ||
| 826 | } else { | ||
| 827 | let (path, index) = | ||
| 828 | find(page, *object).ok_or_else(|| invalid("The object is not on the page"))?; | ||
| 829 | let list = list_at(page, &path); | ||
| 830 | let range = subtree(list, index); | ||
| 831 | if !path.cells.is_empty() && range.len() == list.len() { | ||
| 832 | return Err(invalid( | ||
| 833 | "A table cell keeps a paragraph; insert its replacement first", | ||
| 834 | )); | ||
| 835 | } | ||
| 836 | list.drain(range); | ||
| 837 | } | ||
| 838 | drop_emptied(page); | ||
| 839 | retitle(page, None); | ||
| 840 | } | ||
| 841 | PageOp::Level { paragraph, level } => { | ||
| 842 | let (path, index) = find(page, *paragraph).ok_or_else(|| invalid("The paragraph is not on the page"))?; | ||
| 843 | relevel(list_at(page, &path), index, *level); | ||
| 844 | } | ||
| 845 | PageOp::Outline { object, edit } => match edit { | ||
| 846 | OutlineEdit::Collapsed(value) => paragraph_mut(page, *object)?.collapsed = *value, | ||
| 847 | OutlineEdit::Position { x, y } => { | ||
| 848 | let outline = outline_mut(page, *object)?; | ||
| 849 | outline.layout.x = Some(stored(*x)); | ||
| 850 | outline.layout.y = Some(stored(*y)); | ||
| 851 | retitle(page, None); | ||
| 852 | } | ||
| 853 | OutlineEdit::Width { points, user_set } => { | ||
| 854 | let outline = outline_mut(page, *object)?; | ||
| 855 | outline.layout.max_width = Some(stored(*points)); | ||
| 856 | outline.layout.width_set_by_user = Some(*user_set); | ||
| 857 | outline.layout.reserved_width = None; | ||
| 858 | } | ||
| 859 | }, | ||
| 860 | PageOp::Paragraph { | ||
| 861 | paragraph, | ||
| 862 | alignment, | ||
| 863 | rtl, | ||
| 864 | space_before, | ||
| 865 | space_after, | ||
| 866 | line_spacing, | ||
| 867 | language, | ||
| 868 | } => { | ||
| 869 | let text = &mut paragraph_mut(page, *paragraph)? | ||
| 870 | .text_mut() | ||
| 871 | .ok_or_else(|| invalid("Paragraph formatting belongs to text"))? | ||
| 872 | .text; | ||
| 873 | let mut previous = 0; | ||
| 874 | let runs: Vec<(String, Format)> = text | ||
| 875 | .spans() | ||
| 876 | .iter() | ||
| 877 | .map(|span| { | ||
| 878 | let mut format = span.format.clone(); | ||
| 879 | format.alignment = alignment.or(format.alignment); | ||
| 880 | format.rtl = rtl.or(format.rtl); | ||
| 881 | format.space_before = space_before.map(stored).or(format.space_before); | ||
| 882 | format.space_after = space_after.map(stored).or(format.space_after); | ||
| 883 | format.line_spacing = line_spacing.map(stored).or(format.line_spacing); | ||
| 884 | // A run's own language outranks the text's. | ||
| 885 | format.language = format.language.or(*language); | ||
| 886 | let run = (text.text()[previous..span.end].to_owned(), format); | ||
| 887 | previous = span.end; | ||
| 888 | run | ||
| 889 | }) | ||
| 890 | .collect(); | ||
| 891 | *text = Paragraph::from_runs(runs); | ||
| 892 | } | ||
| 893 | PageOp::Style { | ||
| 894 | paragraph, | ||
| 895 | style, | ||
| 896 | definition, | ||
| 897 | } => { | ||
| 898 | let old = paragraph_mut(page, *paragraph)? | ||
| 899 | .style | ||
| 900 | .and_then(|style| page.definitions.get(&style)) | ||
| 901 | .map(|definition| definition.format.clone()) | ||
| 902 | .unwrap_or_default(); | ||
| 903 | let style = define(page, *style, definition); | ||
| 904 | let new = page.definitions[&style].format.clone(); | ||
| 905 | let paragraph = paragraph_mut(page, *paragraph)?; | ||
| 906 | paragraph.style = Some(style); | ||
| 907 | if let Some(text) = paragraph.text_mut() { | ||
| 908 | let mut previous = 0; | ||
| 909 | let runs: Vec<(String, Format)> = text | ||
| 910 | .text | ||
| 911 | .spans() | ||
| 912 | .iter() | ||
| 913 | .map(|span| { | ||
| 914 | // What the old style gave, the new one gives instead. | ||
| 915 | let mut format = span.format.clone(); | ||
| 916 | macro_rules! restyle { | ||
| 917 | ($($field:ident),*) => {$( | ||
| 918 | if old.$field.is_some() && format.$field == old.$field { | ||
| 919 | format.$field = new.$field.clone(); | ||
| 920 | } else if format.$field.is_none() { | ||
| 921 | format.$field = new.$field.clone(); | ||
| 922 | } | ||
| 923 | )*}; | ||
| 924 | } | ||
| 925 | restyle!(bold, italic, underline, strike, superscript, subscript, hidden, | ||
| 926 | hyperlink, hyperlink_label, math, embedded_object, font, font_size, | ||
| 927 | color, highlight, language, alignment, rtl, space_before, space_after, | ||
| 928 | line_spacing, list_spacing, math_object); | ||
| 929 | let run = (text.text.text()[previous..span.end].to_owned(), format); | ||
| 930 | previous = span.end; | ||
| 931 | run | ||
| 932 | }) | ||
| 933 | .collect(); | ||
| 934 | text.text = Paragraph::from_runs(runs); | ||
| 935 | } | ||
| 936 | } | ||
| 937 | PageOp::List { paragraph, lists } => { | ||
| 938 | paragraph_mut(page, *paragraph)?.lists = lists.iter().map(|(id, _)| *id).collect(); | ||
| 939 | for (id, definition) in lists { | ||
| 940 | page.definitions.insert(*id, definition.clone()); | ||
| 941 | } | ||
| 942 | } | ||
| 943 | PageOp::Tags { | ||
| 944 | target, | ||
| 945 | tags, | ||
| 946 | definitions, | ||
| 947 | } => { | ||
| 948 | let mut tags = tags.clone(); | ||
| 949 | for tag in &mut tags { | ||
| 950 | if let Some(id) = &mut tag.definition | ||
| 951 | && let Some((_, definition)) = definitions.iter().find(|(d, _)| d == id) | ||
| 952 | { | ||
| 953 | *id = define(page, *id, definition); | ||
| 954 | } | ||
| 955 | } | ||
| 956 | if let Ok(paragraph) = paragraph_mut(page, *target) { | ||
| 957 | paragraph.tags = tags; | ||
| 958 | } else if let Ok(text) = text_mut(page, *target) { | ||
| 959 | text.tags = tags; | ||
| 960 | } else { | ||
| 961 | table_mut(page, *target)?.tags = tags; | ||
| 962 | } | ||
| 963 | } | ||
| 964 | PageOp::Add { object, before } => { | ||
| 965 | let object = match object { | ||
| 966 | PageObject::Outline(outline) => { | ||
| 967 | let mut outline = outline.clone(); | ||
| 968 | outline.paragraphs = insertion(&outline.paragraphs); | ||
| 969 | if outline.indents.is_empty() { | ||
| 970 | outline.indents = NATIVE_INDENTS.to_vec(); | ||
| 971 | } | ||
| 972 | let layout = &mut outline.layout; | ||
| 973 | layout.x = layout.x.map(stored); | ||
| 974 | layout.y = layout.y.map(stored); | ||
| 975 | match layout.max_width { | ||
| 976 | Some(points) if (points, layout.width_set_by_user) != (468.0, None) => { | ||
| 977 | layout.max_width = Some(stored(points)); | ||
| 978 | layout.width_set_by_user = Some(layout.width_set_by_user == Some(true)); | ||
| 979 | } | ||
| 980 | _ => { | ||
| 981 | layout.max_width = Some(468.0); | ||
| 982 | layout.width_set_by_user = None; | ||
| 983 | } | ||
| 984 | } | ||
| 985 | layout.reserved_width = None; | ||
| 986 | layout.max_height = Some(0.6_f32 * 36.0); | ||
| 987 | PageObject::Outline(outline) | ||
| 988 | } | ||
| 989 | PageObject::Ink(ink) => PageObject::Ink(inked(ink)), | ||
| 990 | object => object.clone(), | ||
| 991 | }; | ||
| 992 | let outline = matches!(object, PageObject::Outline(_)); | ||
| 993 | let at = page_position(page, *before)?; | ||
| 994 | page.objects.insert(at, object); | ||
| 995 | if outline { | ||
| 996 | retitle(page, None); | ||
| 997 | } | ||
| 998 | } | ||
| 999 | PageOp::Picture { | ||
| 1000 | picture, | ||
| 1001 | layout, | ||
| 1002 | alt, | ||
| 1003 | } => { | ||
| 1004 | let image = image_mut(page, *picture)?; | ||
| 1005 | image.layout = crate::document::Layout { | ||
| 1006 | x: layout.x.map(stored), | ||
| 1007 | y: layout.y.map(stored), | ||
| 1008 | max_width: layout.max_width.map(stored), | ||
| 1009 | max_height: layout.max_height.map(stored), | ||
| 1010 | width_set_by_user: layout.max_width.map(|_| layout.width_set_by_user == Some(true)), | ||
| 1011 | reserved_width: image.layout.reserved_width, | ||
| 1012 | }; | ||
| 1013 | image.alt = alt.clone(); | ||
| 1014 | } | ||
| 1015 | PageOp::Attachment { | ||
| 1016 | attachment, | ||
| 1017 | filename, | ||
| 1018 | source_path, | ||
| 1019 | size, | ||
| 1020 | } => { | ||
| 1021 | let (path, index) = lists(page) | ||
| 1022 | .into_iter() | ||
| 1023 | .find_map(|(path, _, list)| { | ||
| 1024 | list.iter() | ||
| 1025 | .position(|p| matches!(&p.content, ParagraphContent::Attachment(a) if a.id == *attachment)) | ||
| 1026 | .map(|index| (path, index)) | ||
| 1027 | }) | ||
| 1028 | .ok_or_else(|| invalid("The attachment is not on the page"))?; | ||
| 1029 | let ParagraphContent::Attachment(stored_attachment) = &mut list_at(page, &path)[index].content | ||
| 1030 | else { | ||
| 1031 | unreachable!() | ||
| 1032 | }; | ||
| 1033 | stored_attachment.filename = filename.clone(); | ||
| 1034 | stored_attachment.source_path = source_path.clone(); | ||
| 1035 | stored_attachment.size = size.map(|[w, h]| [stored(w), stored(h)]); | ||
| 1036 | } | ||
| 1037 | PageOp::Strokes { ink, add, remove } => { | ||
| 1038 | let ink = ink_mut(page, *ink)?; | ||
| 1039 | ink.strokes.retain(|stroke| !remove.contains(&stroke.id)); | ||
| 1040 | ink.strokes.extend(add.iter().map(stroked)); | ||
| 1041 | } | ||
| 1042 | PageOp::Table { table, edit } => { | ||
| 1043 | let table = table_mut(page, *table)?; | ||
| 1044 | match edit { | ||
| 1045 | TableEdit::Rows { before, rows } => { | ||
| 1046 | let at = match before { | ||
| 1047 | Some(before) => table | ||
| 1048 | .rows | ||
| 1049 | .iter() | ||
| 1050 | .position(|r| r.id == *before) | ||
| 1051 | .ok_or_else(|| invalid("The anchor row is not in the table"))?, | ||
| 1052 | None => table.rows.len(), | ||
| 1053 | }; | ||
| 1054 | let mut rows = rows.clone(); | ||
| 1055 | for cell in rows.iter_mut().flat_map(|row| &mut row.cells) { | ||
| 1056 | if cell.indents.is_empty() { | ||
| 1057 | cell.indents = table | ||
| 1058 | .rows | ||
| 1059 | .first() | ||
| 1060 | .and_then(|r| r.cells.first()) | ||
| 1061 | .map_or(NATIVE_INDENTS.to_vec(), |c| c.indents.clone()); | ||
| 1062 | } | ||
| 1063 | cell.paragraphs = insertion(&cell.paragraphs); | ||
| 1064 | } | ||
| 1065 | table.rows.splice(at..at, rows); | ||
| 1066 | } | ||
| 1067 | TableEdit::Column { at, width, cells } => { | ||
| 1068 | let template = table | ||
| 1069 | .rows | ||
| 1070 | .first() | ||
| 1071 | .and_then(|r| r.cells.first()) | ||
| 1072 | .map(|c| c.indents.clone()); | ||
| 1073 | for (row, cell) in table.rows.iter_mut().zip(cells) { | ||
| 1074 | let mut cell = cell.clone(); | ||
| 1075 | if cell.indents.is_empty() { | ||
| 1076 | cell.indents = template.clone().unwrap_or(NATIVE_INDENTS.to_vec()); | ||
| 1077 | } | ||
| 1078 | cell.paragraphs = insertion(&cell.paragraphs); | ||
| 1079 | row.cells.insert(*at as usize, cell); | ||
| 1080 | } | ||
| 1081 | table.columns.insert( | ||
| 1082 | *at as usize, | ||
| 1083 | crate::page::TableColumn { | ||
| 1084 | width: stored(*width), | ||
| 1085 | locked: false, | ||
| 1086 | }, | ||
| 1087 | ); | ||
| 1088 | } | ||
| 1089 | TableEdit::DeleteRow(row) => table.rows.retain(|r| r.id != *row), | ||
| 1090 | TableEdit::DeleteColumn(at) => { | ||
| 1091 | table.columns.remove(*at as usize); | ||
| 1092 | for row in &mut table.rows { | ||
| 1093 | row.cells.remove(*at as usize); | ||
| 1094 | } | ||
| 1095 | } | ||
| 1096 | TableEdit::Columns(columns) => { | ||
| 1097 | table.columns = columns | ||
| 1098 | .iter() | ||
| 1099 | .map(|c| crate::page::TableColumn { | ||
| 1100 | width: stored(c.width), | ||
| 1101 | locked: c.locked, | ||
| 1102 | }) | ||
| 1103 | .collect(); | ||
| 1104 | } | ||
| 1105 | TableEdit::Borders(borders) => table.borders = Some(*borders), | ||
| 1106 | TableEdit::Cell { | ||
| 1107 | cell, | ||
| 1108 | shading, | ||
| 1109 | indents, | ||
| 1110 | } => { | ||
| 1111 | let cell = table | ||
| 1112 | .rows | ||
| 1113 | .iter_mut() | ||
| 1114 | .flat_map(|row| &mut row.cells) | ||
| 1115 | .find(|c| c.id == *cell) | ||
| 1116 | .ok_or_else(|| invalid("The cell is not in the table"))?; | ||
| 1117 | cell.shading = *shading; | ||
| 1118 | cell.indents = indents.iter().map(|v| stored(*v)).collect(); | ||
| 1119 | } | ||
| 1120 | } | ||
| 1121 | } | ||
| 1122 | } | ||
| 1123 | Ok(()) | ||
| 1124 | } | ||
| 1125 | |||
| 1126 | /// Removes outlines a tree edit left without paragraphs, as the tree writer does. | ||
| 1127 | fn drop_emptied(page: &mut Page) { | ||
| 1128 | page.objects | ||
| 1129 | .retain(|object| !matches!(object, PageObject::Outline(outline) if outline.paragraphs.is_empty())); | ||
| 1130 | } | ||
| 1131 | |||
| 1132 | /// The identity a read-only definition is stored under: an equal one the page holds, as | ||
| 1133 | /// the writers share identical read-only objects, else `id`. | ||
| 1134 | fn define(page: &mut Page, id: ExGuid, definition: &crate::page::Definition) -> ExGuid { | ||
| 1135 | if page.definitions.contains_key(&id) { | ||
| 1136 | return id; | ||
| 1137 | } | ||
| 1138 | if let Some((existing, _)) = page.definitions.iter().find(|(_, d)| *d == definition) { | ||
| 1139 | return *existing; | ||
| 1140 | } | ||
| 1141 | page.definitions.insert(id, definition.clone()); | ||
| 1142 | id | ||
| 1143 | } | ||
| 1144 | |||
| 1145 | /// Where a page object goes before `before`, or after the other children; titles, which | ||
| 1146 | /// the page lists apart, read after them. | ||
| 1147 | fn page_position(page: &Page, before: Option<ExGuid>) -> Result<usize, Error> { | ||
| 1148 | match before { | ||
| 1149 | Some(before) => page | ||
| 1150 | .objects | ||
| 1151 | .iter() | ||
| 1152 | .position(|o| o.id() == before) | ||
| 1153 | .ok_or_else(|| invalid("The anchor is not on the page")), | ||
| 1154 | None => Ok(page | ||
| 1155 | .objects | ||
| 1156 | .iter() | ||
| 1157 | .position(|o| matches!(o, PageObject::Title(_))) | ||
| 1158 | .unwrap_or(page.objects.len())), | ||
| 1159 | } | ||
| 1160 | } | ||
| 1161 | |||
| 1162 | fn table_cells(page: &Page) -> BTreeSet<ExGuid> { | ||
| 1163 | lists(page) | ||
| 1164 | .into_iter() | ||
| 1165 | .filter(|(path, _, _)| !path.cells.is_empty()) | ||
| 1166 | .map(|(_, owner, _)| owner) | ||
| 1167 | .collect() | ||
| 1168 | } | ||
| 1169 | |||
| 1170 | fn outline_mut(page: &mut Page, id: ExGuid) -> Result<&mut Outline, Error> { | ||
| 1171 | page.objects | ||
| 1172 | .iter_mut() | ||
| 1173 | .find_map(|object| match object { | ||
| 1174 | PageObject::Outline(outline) if outline.id == id => Some(outline), | ||
| 1175 | _ => None, | ||
| 1176 | }) | ||
| 1177 | .ok_or_else(|| invalid("The outline is not on the page")) | ||
| 1178 | } | ||
| 1179 | |||
| 1180 | fn image_mut(page: &mut Page, id: ExGuid) -> Result<&mut crate::page::Image, Error> { | ||
| 1181 | if let Some(at) = page.objects.iter().position(|o| o.id() == id) { | ||
| 1182 | let PageObject::Image(image) = &mut page.objects[at] else { | ||
| 1183 | return Err(invalid("Select a picture")); | ||
| 1184 | }; | ||
| 1185 | return Ok(image); | ||
| 1186 | } | ||
| 1187 | let (path, index) = lists(page) | ||
| 1188 | .into_iter() | ||
| 1189 | .find_map(|(path, _, list)| { | ||
| 1190 | list.iter() | ||
| 1191 | .position(|p| matches!(&p.content, ParagraphContent::Image(i) if i.id == id)) | ||
| 1192 | .map(|index| (path, index)) | ||
| 1193 | }) | ||
| 1194 | .ok_or_else(|| invalid("The picture is not on the page"))?; | ||
| 1195 | let ParagraphContent::Image(image) = &mut list_at(page, &path)[index].content else { | ||
| 1196 | unreachable!() | ||
| 1197 | }; | ||
| 1198 | Ok(image) | ||
| 1199 | } | ||
| 1200 | |||
| 1201 | fn ink_mut(page: &mut Page, id: ExGuid) -> Result<&mut crate::page::Ink, Error> { | ||
| 1202 | if let Some(at) = page.objects.iter().position(|o| o.id() == id) { | ||
| 1203 | let PageObject::Ink(ink) = &mut page.objects[at] else { | ||
| 1204 | return Err(invalid("Select ink")); | ||
| 1205 | }; | ||
| 1206 | return Ok(ink); | ||
| 1207 | } | ||
| 1208 | let (path, index) = lists(page) | ||
| 1209 | .into_iter() | ||
| 1210 | .find_map(|(path, _, list)| { | ||
| 1211 | list.iter() | ||
| 1212 | .position(|p| matches!(&p.content, ParagraphContent::Ink(i) if i.id == id)) | ||
| 1213 | .map(|index| (path, index)) | ||
| 1214 | }) | ||
| 1215 | .ok_or_else(|| invalid("The ink is not on the page"))?; | ||
| 1216 | let ParagraphContent::Ink(ink) = &mut list_at(page, &path)[index].content else { | ||
| 1217 | unreachable!() | ||
| 1218 | }; | ||
| 1219 | Ok(ink) | ||
| 1220 | } | ||
| 1221 | |||
| 1222 | /// The text object of the page title, when it has one. | ||
| 1223 | fn find_title_text(page: &Page) -> Option<ExGuid> { | ||
| 1224 | page.objects | ||
| 1225 | .iter() | ||
| 1226 | .filter_map(|object| match object { | ||
| 1227 | PageObject::Title(title) => Some(title), | ||
| 1228 | _ => None, | ||
| 1229 | }) | ||
| 1230 | .flat_map(|title| title.outlines.iter().filter(|o| Some(o.id) != title.date)) | ||
| 1231 | .flat_map(|outline| &outline.paragraphs) | ||
| 1232 | .filter_map(|p| p.text().filter(|t| t.date_field.is_none())) | ||
| 1233 | .map(|t| t.id) | ||
| 1234 | .next_back() | ||
| 1235 | } | ||
| 1236 | |||
| 1237 | fn text_of(page: &Page, text: ExGuid) -> String { | ||
| 1238 | lists(page) | ||
| 1239 | .into_iter() | ||
| 1240 | .find_map(|(_, _, list)| { | ||
| 1241 | list.iter() | ||
| 1242 | .find_map(|p| p.text().filter(|t| t.id == text).map(|t| t.text.text().to_owned())) | ||
| 1243 | }) | ||
| 1244 | .unwrap_or_default() | ||
| 1245 | } | ||
crates/onestore/src/op/properties.rs created+376| ... | @@ -0,0 +1,376 @@ | ||
| 1 | //! Paragraph properties: list nodes, note tags, paragraph styles and paragraph formatting. | ||
| 2 | |||
| 3 | use super::Values; | ||
| 4 | use crate::{ | ||
| 5 | Error, ExGuid, PropertySets, Value, | ||
| 6 | active::{ActivePage, Changes}, | ||
| 7 | document::{Kind, Tag}, | ||
| 8 | page::Definition, | ||
| 9 | write::PropertyObject, | ||
| 10 | }; | ||
| 11 | use std::collections::{BTreeMap, BTreeSet}; | ||
| 12 | |||
| 13 | fn invalid(message: &'static str) -> Error { | ||
| 14 | Error { offset: 0, message } | ||
| 15 | } | ||
| 16 | |||
| 17 | /// The properties of a list node carrying `definition`. | ||
| 18 | pub(crate) fn list_values(definition: &Definition) -> Result<Values, Error> { | ||
| 19 | let Kind::List { | ||
| 20 | font, | ||
| 21 | format, | ||
| 22 | restart, | ||
| 23 | bullet, | ||
| 24 | } = &definition.kind | ||
| 25 | else { | ||
| 26 | return Err(invalid("A paragraph list must reference a list definition")); | ||
| 27 | }; | ||
| 28 | if bullet.is_some() && (format.is_none() || font.is_none()) { | ||
| 29 | return Err(invalid("A bullet definition names its glyph and font")); | ||
| 30 | } | ||
| 31 | let mut values: Values = Vec::new(); | ||
| 32 | if let Some(format) = format { | ||
| 33 | let units: Vec<u16> = format.encode_utf16().collect(); | ||
| 34 | let count = u16::try_from(units.len()) | ||
| 35 | .map_err(|_| invalid("List format exceeds the document range"))?; | ||
| 36 | let mut bytes = count.to_le_bytes().to_vec(); | ||
| 37 | bytes.extend(units.iter().flat_map(|unit| unit.to_le_bytes())); | ||
| 38 | values.push((0x1c001c1a, bytes)); | ||
| 39 | } | ||
| 40 | if let Some(font) = font { | ||
| 41 | values.push((0x1c001c52, crate::create::string(font))); | ||
| 42 | } | ||
| 43 | if let Some(restart) = restart { | ||
| 44 | values.push((0x14001cb7, restart.to_le_bytes().to_vec())); | ||
| 45 | } | ||
| 46 | if let Some(bullet) = bullet { | ||
| 47 | values.push((0x10001d0e, bullet.to_le_bytes().to_vec())); | ||
| 48 | // Every native bullet node carries this cleared flag alongside its index. | ||
| 49 | values.push((0x0c001cc0, vec![0])); | ||
| 50 | } | ||
| 51 | let style = &definition.format; | ||
| 52 | if let Some(font) = &style.font { | ||
| 53 | values.push((0x1c001c0a, crate::create::string(font))); | ||
| 54 | } | ||
| 55 | if let Some(size) = style.font_size { | ||
| 56 | let half = (size * 2.0).round(); | ||
| 57 | if !(0.0..=f32::from(u16::MAX)).contains(&half) { | ||
| 58 | return Err(invalid("List font size is outside the document range")); | ||
| 59 | } | ||
| 60 | values.push((0x10001c0b, (half as u16).to_le_bytes().to_vec())); | ||
| 61 | } | ||
| 62 | if let Some(color) = style.color { | ||
| 63 | values.push((0x14001c0c, color.to_le_bytes().to_vec())); | ||
| 64 | } | ||
| 65 | if let Some(language) = style.language { | ||
| 66 | values.push((0x14001c3b, language.to_le_bytes().to_vec())); | ||
| 67 | } | ||
| 68 | for (flag, id) in [(style.bold, 0x08001c04), (style.italic, 0x08001c05)] { | ||
| 69 | if let Some(flag) = flag { | ||
| 70 | values.push((id | (u32::from(flag) << 31), Vec::new())); | ||
| 71 | } | ||
| 72 | } | ||
| 73 | Ok(values) | ||
| 74 | } | ||
| 75 | |||
| 76 | /// Gives `paragraph` the list nodes `nodes` names, rewriting stored ones to their values | ||
| 77 | /// and creating the rest. | ||
| 78 | pub(crate) fn list_changes( | ||
| 79 | active: &ActivePage<'_>, | ||
| 80 | paragraph: ExGuid, | ||
| 81 | nodes: &[(ExGuid, Values)], | ||
| 82 | ) -> Result<Changes, Error> { | ||
| 83 | let parents = active.editable_parents(paragraph)?; | ||
| 84 | let modified = crate::create::current_timestamps()?.0.to_le_bytes(); | ||
| 85 | let raw = &active.live.revision; | ||
| 86 | let mut changed = BTreeMap::new(); | ||
| 87 | let mut target = PropertyObject::from_object(&raw.objects[&paragraph])?; | ||
| 88 | let mut references = Vec::new(); | ||
| 89 | for (list, values) in nodes { | ||
| 90 | let mut node = match raw.objects.get(list) { | ||
| 91 | Some(existing) => { | ||
| 92 | if existing.jcid != 0x60012 { | ||
| 93 | return Err(invalid( | ||
| 94 | "A list definition identity belongs to another object", | ||
| 95 | )); | ||
| 96 | } | ||
| 97 | let mut node = PropertyObject::from_object(existing)?; | ||
| 98 | node.remove(&[ | ||
| 99 | 0x1c001c1a, 0x1c001c52, 0x14001cb7, 0x10001d0e, 0x0c001cc0, 0x1c001c0a, | ||
| 100 | 0x10001c0b, 0x14001c0c, 0x14001c3b, 0x08001c04, 0x08001c05, | ||
| 101 | ])?; | ||
| 102 | node | ||
| 103 | } | ||
| 104 | None => PropertyObject { | ||
| 105 | jcid: 0x60012, | ||
| 106 | bytes: crate::create::properties(&[])?, | ||
| 107 | global_ids: std::sync::Arc::new(BTreeMap::from([(0, list.guid)])), | ||
| 108 | }, | ||
| 109 | }; | ||
| 110 | node.set( | ||
| 111 | &values | ||
| 112 | .iter() | ||
| 113 | .map(|(id, bytes)| (*id, bytes.as_slice())) | ||
| 114 | .collect::<Vec<_>>(), | ||
| 115 | )?; | ||
| 116 | node.set(&[(0x14001d7a, &modified)])?; | ||
| 117 | node.reference(*list)?; | ||
| 118 | references.extend_from_slice(&target.reference(*list)?); | ||
| 119 | changed.insert(*list, node); | ||
| 120 | } | ||
| 121 | if references.is_empty() { | ||
| 122 | target.remove(&[0x24001c26])?; | ||
| 123 | } else { | ||
| 124 | target.set(&[(0x24001c26, &references)])?; | ||
| 125 | } | ||
| 126 | target.set(&[(0x14001d7a, &modified)])?; | ||
| 127 | changed.insert(paragraph, target); | ||
| 128 | crate::formatting::touch_ancestors(raw, parents, paragraph, &modified, &mut changed)?; | ||
| 129 | Ok(changed) | ||
| 130 | } | ||
| 131 | |||
| 132 | /// The properties of a new tag definition object. | ||
| 133 | fn tag_definition_values(definition: &Definition) -> Result<Values, Error> { | ||
| 134 | let Kind::TagDefinition { | ||
| 135 | label, | ||
| 136 | action_type, | ||
| 137 | shape, | ||
| 138 | color, | ||
| 139 | highlight, | ||
| 140 | } = &definition.kind | ||
| 141 | else { | ||
| 142 | return Err(invalid("A note tag must reference a tag definition")); | ||
| 143 | }; | ||
| 144 | let mut values: Values = vec![ | ||
| 145 | (0x0c003473, vec![0]), | ||
| 146 | (0x10003463, action_type.unwrap_or(0).to_le_bytes().to_vec()), | ||
| 147 | (0x10003464, shape.unwrap_or(0).to_le_bytes().to_vec()), | ||
| 148 | (0x14003467, 0u32.to_le_bytes().to_vec()), | ||
| 149 | ]; | ||
| 150 | if let Some(label) = label { | ||
| 151 | values.push((0x1c003468, crate::create::string(label))); | ||
| 152 | } | ||
| 153 | if let Some(color) = color { | ||
| 154 | values.push((0x14003466, color.to_le_bytes().to_vec())); | ||
| 155 | } | ||
| 156 | if let Some(highlight) = highlight { | ||
| 157 | values.push((0x14003465, highlight.to_le_bytes().to_vec())); | ||
| 158 | } | ||
| 159 | Ok(values) | ||
| 160 | } | ||
| 161 | |||
| 162 | /// Replaces the note tags of `object`. Each entry names the stored identity of its tag's | ||
| 163 | /// definition and, where the tag does not carry its action type or the page lacks the | ||
| 164 | /// definition, the definition itself. | ||
| 165 | pub(crate) fn tag_changes( | ||
| 166 | active: &ActivePage<'_>, | ||
| 167 | object: ExGuid, | ||
| 168 | tags: &[(ExGuid, &Tag, Option<&Definition>)], | ||
| 169 | ) -> Result<Changes, Error> { | ||
| 170 | let raw = &active.live.revision; | ||
| 171 | let mut definitions: Vec<(ExGuid, Option<Values>)> = Vec::new(); | ||
| 172 | let mut sets: Vec<(usize, Values)> = Vec::new(); | ||
| 173 | let mut action_types = BTreeSet::new(); | ||
| 174 | for (written, tag, definition) in tags { | ||
| 175 | let action_type = if tag.status & 4 != 0 { | ||
| 176 | tag.action_type | ||
| 177 | } else { | ||
| 178 | match definition.map(|d| &d.kind) { | ||
| 179 | Some(Kind::TagDefinition { action_type, .. }) => *action_type, | ||
| 180 | _ => return Err(invalid("A note tag must reference a tag definition")), | ||
| 181 | } | ||
| 182 | }; | ||
| 183 | if !action_types.insert(action_type.unwrap_or(0)) { | ||
| 184 | return Err(invalid("An element holds one note tag per action type")); | ||
| 185 | } | ||
| 186 | let index = match definitions.iter().position(|(id, _)| id == written) { | ||
| 187 | Some(index) => index, | ||
| 188 | None => { | ||
| 189 | let values = if raw.objects.contains_key(written) { | ||
| 190 | None | ||
| 191 | } else { | ||
| 192 | let definition = definition | ||
| 193 | .ok_or_else(|| invalid("A note tag references a missing tag definition"))?; | ||
| 194 | Some(tag_definition_values(definition)?) | ||
| 195 | }; | ||
| 196 | definitions.push((*written, values)); | ||
| 197 | definitions.len() - 1 | ||
| 198 | } | ||
| 199 | }; | ||
| 200 | let mut fields: Values = Vec::new(); | ||
| 201 | if let Some(action_type) = tag.action_type { | ||
| 202 | fields.push((0x10003463, action_type.to_le_bytes().to_vec())); | ||
| 203 | } | ||
| 204 | for (id, value) in [ | ||
| 205 | (0x1400346e, tag.created), | ||
| 206 | (0x1400346f, tag.completed), | ||
| 207 | (0x1400346a, tag.start), | ||
| 208 | (0x1400346b, tag.due), | ||
| 209 | ] { | ||
| 210 | if let Some(value) = value { | ||
| 211 | fields.push((id, value.to_le_bytes().to_vec())); | ||
| 212 | } | ||
| 213 | } | ||
| 214 | fields.push((0x10003470, tag.status.to_le_bytes().to_vec())); | ||
| 215 | if let Some(task) = tag.task_id { | ||
| 216 | fields.push((0x1c003469, task.to_vec())); | ||
| 217 | } | ||
| 218 | sets.push((index, fields)); | ||
| 219 | } | ||
| 220 | let parents = active.editable_parents(object)?; | ||
| 221 | let modified = crate::create::current_timestamps()?.0.to_le_bytes(); | ||
| 222 | let mut changed = BTreeMap::new(); | ||
| 223 | for (id, values) in &definitions { | ||
| 224 | let Some(values) = values else { | ||
| 225 | continue; | ||
| 226 | }; | ||
| 227 | let mut node = PropertyObject { | ||
| 228 | jcid: 0x120043, | ||
| 229 | bytes: crate::create::properties(values)?, | ||
| 230 | global_ids: std::sync::Arc::new(BTreeMap::from([(0, id.guid)])), | ||
| 231 | }; | ||
| 232 | node.reference(*id)?; | ||
| 233 | changed.insert(*id, node); | ||
| 234 | } | ||
| 235 | let mut target = PropertyObject::from_object(&raw.objects[&object])?; | ||
| 236 | let mut encoded = Vec::new(); | ||
| 237 | for (index, fields) in &sets { | ||
| 238 | let reference = target.reference(definitions[*index].0)?; | ||
| 239 | let mut set = vec![(0x20003488, reference.to_vec())]; | ||
| 240 | set.extend(fields.iter().cloned()); | ||
| 241 | encoded.push(set); | ||
| 242 | } | ||
| 243 | target.set_sets(0x40003489, 0x44000811, &encoded)?; | ||
| 244 | target.set(&[(0x14001d7a, &modified)])?; | ||
| 245 | changed.insert(object, target); | ||
| 246 | crate::formatting::touch_ancestors(raw, parents, object, &modified, &mut changed)?; | ||
| 247 | Ok(changed) | ||
| 248 | } | ||
| 249 | |||
| 250 | /// Makes `text` reference paragraph style `style`, created from `definition` where the | ||
| 251 | /// page does not store it. | ||
| 252 | pub(crate) fn style_changes( | ||
| 253 | active: &ActivePage<'_>, | ||
| 254 | text: ExGuid, | ||
| 255 | style: ExGuid, | ||
| 256 | definition: Option<&Definition>, | ||
| 257 | ) -> Result<Changes, Error> { | ||
| 258 | let raw = &active.live.revision; | ||
| 259 | let mut changed = BTreeMap::new(); | ||
| 260 | if !raw.objects.contains_key(&style) { | ||
| 261 | let definition = | ||
| 262 | definition.ok_or_else(|| invalid("A paragraph references a missing style definition"))?; | ||
| 263 | let Kind::Style { name } = &definition.kind else { | ||
| 264 | return Err(invalid("A paragraph style must be a style definition")); | ||
| 265 | }; | ||
| 266 | let format = &definition.format; | ||
| 267 | let mut values = super::content::style_values(format); | ||
| 268 | if let Some(name) = name { | ||
| 269 | values.push((0x1c00345a, crate::create::string(name))); | ||
| 270 | } | ||
| 271 | if let Some(alignment) = format.alignment { | ||
| 272 | values.push((0x0c003477, vec![alignment])); | ||
| 273 | } | ||
| 274 | for (property, value) in [ | ||
| 275 | (0x1400342e, format.space_before), | ||
| 276 | (0x1400342f, format.space_after), | ||
| 277 | (0x14003430, format.line_spacing), | ||
| 278 | ] { | ||
| 279 | if let Some(points) = value { | ||
| 280 | values.push((property, (points / 36.0).to_le_bytes().to_vec())); | ||
| 281 | } | ||
| 282 | } | ||
| 283 | let mut node = PropertyObject { | ||
| 284 | jcid: 0x12004d, | ||
| 285 | bytes: crate::create::properties(&values)?, | ||
| 286 | global_ids: std::sync::Arc::new(BTreeMap::from([(0, style.guid)])), | ||
| 287 | }; | ||
| 288 | node.reference(style)?; | ||
| 289 | changed.insert(style, node); | ||
| 290 | } | ||
| 291 | let mut target = PropertyObject::from_object(&raw.objects[&text])?; | ||
| 292 | let reference = target.reference(style)?; | ||
| 293 | target.set(&[(0x2000342c, &reference)])?; | ||
| 294 | changed.insert(text, target); | ||
| 295 | Ok(changed) | ||
| 296 | } | ||
| 297 | |||
| 298 | /// The text-object properties of paragraph formatting, in the order they are written. | ||
| 299 | pub(crate) fn paragraph_values( | ||
| 300 | alignment: Option<u8>, | ||
| 301 | rtl: Option<bool>, | ||
| 302 | space_before: Option<f32>, | ||
| 303 | space_after: Option<f32>, | ||
| 304 | line_spacing: Option<f32>, | ||
| 305 | language: Option<u32>, | ||
| 306 | ) -> Result<Values, Error> { | ||
| 307 | let mut values = Values::new(); | ||
| 308 | if let Some(alignment) = alignment { | ||
| 309 | if alignment > 2 { | ||
| 310 | return Err(invalid("Paragraph alignment must be left, center or right")); | ||
| 311 | } | ||
| 312 | values.push((0x0c003477, vec![alignment])); | ||
| 313 | } | ||
| 314 | if let Some(rtl) = rtl { | ||
| 315 | values.push((0x08003476 | (u32::from(rtl) << 31), Vec::new())); | ||
| 316 | } | ||
| 317 | for (property, value) in [ | ||
| 318 | (0x1400342e, space_before), | ||
| 319 | (0x1400342f, space_after), | ||
| 320 | (0x14003430, line_spacing), | ||
| 321 | ] { | ||
| 322 | if let Some(points) = value { | ||
| 323 | let stored = points / 36.0; | ||
| 324 | if !stored.is_finite() || !(0.0..=27777.777).contains(&stored) { | ||
| 325 | return Err(invalid("Paragraph spacing is outside the document range")); | ||
| 326 | } | ||
| 327 | values.push((property, stored.to_le_bytes().to_vec())); | ||
| 328 | } | ||
| 329 | } | ||
| 330 | if let Some(language) = language { | ||
| 331 | values.push((0x14001c3b, language.to_le_bytes().to_vec())); | ||
| 332 | } | ||
| 333 | Ok(values) | ||
| 334 | } | ||
| 335 | |||
| 336 | /// Writes paragraph formatting `values` on text object `object`; alignment also sets the | ||
| 337 | /// layout alignment fields OneNote keeps beside it. | ||
| 338 | pub(crate) fn paragraph_format_changes( | ||
| 339 | active: &ActivePage<'_>, | ||
| 340 | object: ExGuid, | ||
| 341 | values: &Values, | ||
| 342 | ) -> Result<Changes, Error> { | ||
| 343 | let parents = active.editable_parents(object)?; | ||
| 344 | let modified = crate::create::current_timestamps()?.0.to_le_bytes(); | ||
| 345 | let raw = &active.live.revision; | ||
| 346 | let mut target = PropertyObject::from_object(&raw.objects[&object])?; | ||
| 347 | target.set( | ||
| 348 | &values | ||
| 349 | .iter() | ||
| 350 | .map(|(id, bytes)| (*id, bytes.as_slice())) | ||
| 351 | .collect::<Vec<_>>(), | ||
| 352 | )?; | ||
| 353 | if let Some((_, alignment)) = values.iter().find(|(id, _)| *id == 0x0c003477) { | ||
| 354 | for property in [0x14001c3e, 0x14001c84] { | ||
| 355 | let fields = PropertySets::parse(&target.bytes)?; | ||
| 356 | let previous = fields.sets[0] | ||
| 357 | .iter() | ||
| 358 | .find(|field| field.id == property) | ||
| 359 | .map(|field| match field.value { | ||
| 360 | Value::Bytes(bytes) => bytes | ||
| 361 | .try_into() | ||
| 362 | .map(u32::from_le_bytes) | ||
| 363 | .map_err(|_| invalid("Invalid paragraph layout alignment")), | ||
| 364 | _ => Err(invalid("Invalid paragraph layout alignment")), | ||
| 365 | }) | ||
| 366 | .transpose()? | ||
| 367 | .unwrap_or(0); | ||
| 368 | let value = (previous & !7) | (u32::from(alignment[0]) + 1); | ||
| 369 | target.set(&[(property, &value.to_le_bytes())])?; | ||
| 370 | } | ||
| 371 | } | ||
| 372 | target.set(&[(0x14001d7a, &modified)])?; | ||
| 373 | let mut changed = BTreeMap::from([(object, target)]); | ||
| 374 | crate::formatting::touch_ancestors(raw, parents, object, &modified, &mut changed)?; | ||
| 375 | Ok(changed) | ||
| 376 | } | ||
crates/onestore/src/op/table.rs created+214| ... | @@ -0,0 +1,214 @@ | ||
| 1 | //! Tables as OneNote stores them: rows and cells are plain containers, a cell carries its | ||
| 2 | //! indentation table and the flags every native cell has, the table its column widths, | ||
| 3 | //! locks and border flag. | ||
| 4 | |||
| 5 | use super::{ | ||
| 6 | Values, | ||
| 7 | content::{NATIVE_INDENTS, measurement_bytes}, | ||
| 8 | }; | ||
| 9 | use crate::{ | ||
| 10 | Error, ExGuid, | ||
| 11 | active::{ActivePage, Changes}, | ||
| 12 | page::{Table, TableColumn}, | ||
| 13 | write::PropertyObject, | ||
| 14 | }; | ||
| 15 | use std::collections::{BTreeMap, BTreeSet}; | ||
| 16 | |||
| 17 | fn invalid(message: &'static str) -> Error { | ||
| 18 | Error { offset: 0, message } | ||
| 19 | } | ||
| 20 | |||
| 21 | /// Every row carries one cell per column and widths are usable. | ||
| 22 | pub(crate) fn validate_table(table: &Table) -> Result<(), Error> { | ||
| 23 | if table.rows.is_empty() || table.columns.is_empty() { | ||
| 24 | return Err(invalid("A table needs at least one row and one column")); | ||
| 25 | } | ||
| 26 | if table | ||
| 27 | .columns | ||
| 28 | .iter() | ||
| 29 | .any(|c| !c.width.is_finite() || c.width < 36.0) | ||
| 30 | { | ||
| 31 | return Err(invalid("Table columns are at least 36 points wide")); | ||
| 32 | } | ||
| 33 | for row in &table.rows { | ||
| 34 | if row.cells.len() != table.columns.len() { | ||
| 35 | return Err(invalid("Every table row has one cell per column")); | ||
| 36 | } | ||
| 37 | for cell in &row.cells { | ||
| 38 | // An emptied cell keeps a replacement paragraph, as the tree writer provides. | ||
| 39 | for paragraph in &cell.paragraphs { | ||
| 40 | if let crate::page::ParagraphContent::Table(nested) = &paragraph.content { | ||
| 41 | validate_table(nested)?; | ||
| 42 | } | ||
| 43 | } | ||
| 44 | } | ||
| 45 | } | ||
| 46 | Ok(()) | ||
| 47 | } | ||
| 48 | |||
| 49 | /// A table's rows and cells by stored identity, in order, and what new ones carry. | ||
| 50 | pub(crate) struct Structure<'t> { | ||
| 51 | pub rows: Vec<(ExGuid, Vec<ExGuid>)>, | ||
| 52 | pub new_rows: BTreeSet<ExGuid>, | ||
| 53 | /// The indentation table and shading of each new cell. | ||
| 54 | pub new_cells: BTreeMap<ExGuid, (&'t [f32], Option<u32>)>, | ||
| 55 | pub columns: &'t [TableColumn], | ||
| 56 | pub borders: Option<bool>, | ||
| 57 | } | ||
| 58 | |||
| 59 | /// The indentation table a new cell takes: its own, else the table's first cell's, else | ||
| 60 | /// the one OneNote gives new cells. | ||
| 61 | pub(crate) fn cell_indents<'t>(own: &'t [f32], template: Option<&'t [f32]>) -> &'t [f32] { | ||
| 62 | [own, template.unwrap_or_default(), &NATIVE_INDENTS] | ||
| 63 | .into_iter() | ||
| 64 | .find(|indents| !indents.is_empty()) | ||
| 65 | .unwrap() | ||
| 66 | } | ||
| 67 | |||
| 68 | /// Writes table `table` as `structure`; with `holder`, creates it as that paragraph's content. | ||
| 69 | pub(crate) fn table_changes( | ||
| 70 | active: &ActivePage<'_>, | ||
| 71 | table: ExGuid, | ||
| 72 | holder: Option<ExGuid>, | ||
| 73 | structure: &Structure<'_>, | ||
| 74 | ) -> Result<Changes, Error> { | ||
| 75 | let modified = crate::create::current_timestamps()?.0.to_le_bytes(); | ||
| 76 | let mut created: Vec<(ExGuid, u32, Values)> = Vec::new(); | ||
| 77 | for (row, cells) in &structure.rows { | ||
| 78 | for cell in cells { | ||
| 79 | if let Some((indents, shading)) = structure.new_cells.get(cell) { | ||
| 80 | let mut values: Values = vec![ | ||
| 81 | (0x14001d7a, modified.to_vec()), | ||
| 82 | (0x0c001c13, vec![0]), | ||
| 83 | (0x0c001c03, vec![1]), | ||
| 84 | (0x88001c91, Vec::new()), | ||
| 85 | (0x1c001c12, measurement_bytes(indents, 4)?), | ||
| 86 | ]; | ||
| 87 | if let Some(shading) = shading { | ||
| 88 | values.push((0x14001e26, shading.to_le_bytes().to_vec())); | ||
| 89 | } | ||
| 90 | created.push((*cell, 0x60024, values)); | ||
| 91 | } | ||
| 92 | } | ||
| 93 | if structure.new_rows.contains(row) { | ||
| 94 | created.push((*row, 0x60023, vec![(0x14001d7a, modified.to_vec())])); | ||
| 95 | } | ||
| 96 | } | ||
| 97 | let columns = structure.columns; | ||
| 98 | let mut table_values: Values = vec![ | ||
| 99 | (0x14001d7a, modified.to_vec()), | ||
| 100 | ( | ||
| 101 | 0x14001d57, | ||
| 102 | (structure.rows.len() as u32).to_le_bytes().to_vec(), | ||
| 103 | ), | ||
| 104 | (0x14001d58, (columns.len() as u32).to_le_bytes().to_vec()), | ||
| 105 | ( | ||
| 106 | 0x1c001d66, | ||
| 107 | measurement_bytes(&columns.iter().map(|c| c.width).collect::<Vec<_>>(), 1)?, | ||
| 108 | ), | ||
| 109 | ]; | ||
| 110 | let mut locks = vec![columns.len() as u8]; | ||
| 111 | locks.extend(vec![0u8; columns.len().div_ceil(8)]); | ||
| 112 | for (i, column) in columns.iter().enumerate() { | ||
| 113 | if column.locked { | ||
| 114 | locks[1 + i / 8] |= 1 << (i % 8); | ||
| 115 | } | ||
| 116 | } | ||
| 117 | table_values.push((0x1c001d7d, locks)); | ||
| 118 | table_values.push(( | ||
| 119 | 0x08001d5e | (u32::from(structure.borders.unwrap_or(true)) << 31), | ||
| 120 | Vec::new(), | ||
| 121 | )); | ||
| 122 | if holder.is_some() { | ||
| 123 | table_values.push((0x14001c3e, 1u32.to_le_bytes().to_vec())); | ||
| 124 | table_values.push((0x14001c84, 1u32.to_le_bytes().to_vec())); | ||
| 125 | created.push((table, 0x60022, Vec::new())); | ||
| 126 | } | ||
| 127 | let raw = &active.live.revision; | ||
| 128 | let mut changed: Changes = BTreeMap::new(); | ||
| 129 | for (id, jcid, values) in &created { | ||
| 130 | let mut node = PropertyObject { | ||
| 131 | jcid: *jcid, | ||
| 132 | bytes: crate::create::properties(values)?, | ||
| 133 | global_ids: std::sync::Arc::new(BTreeMap::from([(0, id.guid)])), | ||
| 134 | }; | ||
| 135 | node.reference(*id)?; | ||
| 136 | changed.insert(*id, node); | ||
| 137 | } | ||
| 138 | let mut row_refs = Vec::new(); | ||
| 139 | for (row_id, cells) in &structure.rows { | ||
| 140 | let mut row = match changed.remove(row_id) { | ||
| 141 | Some(row) => row, | ||
| 142 | None => PropertyObject::from_object(&raw.objects[row_id])?, | ||
| 143 | }; | ||
| 144 | let mut cell_refs = Vec::new(); | ||
| 145 | for cell in cells { | ||
| 146 | cell_refs.extend_from_slice(&row.reference(*cell)?); | ||
| 147 | } | ||
| 148 | row.set(&[(0x24001c20, &cell_refs), (0x14001d7a, &modified)])?; | ||
| 149 | changed.insert(*row_id, row); | ||
| 150 | } | ||
| 151 | let mut table_object = match changed.remove(&table) { | ||
| 152 | Some(object) => object, | ||
| 153 | None => PropertyObject::from_object(&raw.objects[&table])?, | ||
| 154 | }; | ||
| 155 | for (row_id, _) in &structure.rows { | ||
| 156 | row_refs.extend_from_slice(&table_object.reference(*row_id)?); | ||
| 157 | } | ||
| 158 | table_object.remove(&[0x1c001d7d, 0x08001d5e])?; | ||
| 159 | table_object.set( | ||
| 160 | &table_values | ||
| 161 | .iter() | ||
| 162 | .map(|(id, bytes)| (*id, bytes.as_slice())) | ||
| 163 | .collect::<Vec<_>>(), | ||
| 164 | )?; | ||
| 165 | table_object.set(&[(0x24001c20, &row_refs)])?; | ||
| 166 | changed.insert(table, table_object); | ||
| 167 | if let Some(holder) = holder { | ||
| 168 | let mut object = PropertyObject::from_object(&raw.objects[&holder])?; | ||
| 169 | let reference = object.reference(table)?; | ||
| 170 | object.set(&[(0x24001c1f, &reference), (0x14001d7a, &modified)])?; | ||
| 171 | changed.insert(holder, object); | ||
| 172 | } | ||
| 173 | Ok(changed) | ||
| 174 | } | ||
| 175 | |||
| 176 | /// Shading (the documented `CellShadingColor`, which OneNote 2010 stores but neither | ||
| 177 | /// renders nor accepts through its COM schema) and indents change in place on a cell. | ||
| 178 | pub(crate) fn cell_changes( | ||
| 179 | active: &ActivePage<'_>, | ||
| 180 | cell: ExGuid, | ||
| 181 | stored: (Option<u32>, &[f32]), | ||
| 182 | shading: Option<u32>, | ||
| 183 | indents: &[f32], | ||
| 184 | ) -> Result<Changes, Error> { | ||
| 185 | let mut values: Values = vec![( | ||
| 186 | 0x14001d7a, | ||
| 187 | crate::create::current_timestamps()? | ||
| 188 | .0 | ||
| 189 | .to_le_bytes() | ||
| 190 | .to_vec(), | ||
| 191 | )]; | ||
| 192 | let mut removed = Vec::new(); | ||
| 193 | if shading != stored.0 { | ||
| 194 | match shading { | ||
| 195 | Some(shading) => values.push((0x14001e26, shading.to_le_bytes().to_vec())), | ||
| 196 | None => removed.push(0x14001e26), | ||
| 197 | } | ||
| 198 | } | ||
| 199 | if indents != stored.1 { | ||
| 200 | if indents.is_empty() { | ||
| 201 | return Err(invalid("A table cell needs an indentation table")); | ||
| 202 | } | ||
| 203 | values.push((0x1c001c12, measurement_bytes(indents, 4)?)); | ||
| 204 | } | ||
| 205 | let raw = &active.live.revision; | ||
| 206 | let mut object = PropertyObject::from_object(&raw.objects[&cell])?; | ||
| 207 | object.remove(&removed)?; | ||
| 208 | let values: Vec<(u32, &[u8])> = values | ||
| 209 | .iter() | ||
| 210 | .map(|(id, bytes)| (*id, bytes.as_slice())) | ||
| 211 | .collect(); | ||
| 212 | object.set(&values)?; | ||
| 213 | Ok(BTreeMap::from([(cell, object)])) | ||
| 214 | } | ||
crates/onestore/src/op/tests.rs created+2150| ... | @@ -0,0 +1,2150 @@ | ||
| 1 | //! Ops against the page-model writer: for random edits of corpus pages, lowering the edit | ||
| 2 | //! and applying the ops through a `Section` stores what `PreparedEdit::page` stores. | ||
| 3 | |||
| 4 | use super::*; | ||
| 5 | use crate::{ | ||
| 6 | Arena, RevisionIndex, Section, Store, | ||
| 7 | document::{Document, Format, Kind, Layout}, | ||
| 8 | page::{ | ||
| 9 | Attachment, Definition, Image, Ink, InkStroke, Outline, PageObject, PageParagraph, | ||
| 10 | ParagraphContent, Table, TableCell, TableColumn, TableRow, TextObject, text::new_id, | ||
| 11 | }, | ||
| 12 | write::GUIDS, | ||
| 13 | }; | ||
| 14 | use std::collections::BTreeMap; | ||
| 15 | |||
| 16 | const SOURCES: &[(&str, &[u8])] = &[ | ||
| 17 | ("outline-edit", include_bytes!("../../../../corpus/outline-edit/before/notebook/synthetic.one")), | ||
| 18 | ("paragraph-edit", include_bytes!("../../../../corpus/paragraph-edit/before/notebook/synthetic.one")), | ||
| 19 | ("outline-tree", include_bytes!("../../../../corpus/outline-edit/tree/before/notebook/synthetic.one")), | ||
| 20 | ("features", include_bytes!("../../../../corpus/m6/native-features-01/notebook/Features.one")), | ||
| 21 | ("tables", include_bytes!("../../../../corpus/table-edit/nested/cold/notebook/synthetic.one")), | ||
| 22 | ("table-controls", include_bytes!("../../../../corpus/m6/native-table-controls-01/notebook/synthetic.one")), | ||
| 23 | ("lists", include_bytes!("../../../../corpus/list-edit/cold/notebook/lists.one")), | ||
| 24 | ("tags", include_bytes!("../../../../corpus/tag-edit/cold/notebook/tags.one")), | ||
| 25 | ("pictures", include_bytes!("../../../../corpus/picture-edit/native-page-level/notebook/pictures.one")), | ||
| 26 | ("attachments", include_bytes!("../../../../corpus/attachment-edit/plain/cold/notebook/files.one")), | ||
| 27 | ("ink", include_bytes!("../../../../corpus/ink-edit/drawing/cold/notebook/ink.one")), | ||
| 28 | ("math", include_bytes!("../../../../corpus/math-edit/native-editor/notebook/links.one")), | ||
| 29 | ("links", include_bytes!("../../../../corpus/link-edit/native-links/notebook/links.one")), | ||
| 30 | ("paragraph-format", include_bytes!("../../../../corpus/paragraph-format/cold/notebook/synthetic.one")), | ||
| 31 | ("enter-probe", include_bytes!("../../../../evidence/structural-edits/probe-section/probe.one")), | ||
| 32 | ]; | ||
| 33 | |||
| 34 | /// FILETIME both writers take as the edit's time. | ||
| 35 | const AT: u64 = 133_700_000_000_000_000; | ||
| 36 | |||
| 37 | pub(super) struct Rng(pub(super) u64); | ||
| 38 | |||
| 39 | impl Rng { | ||
| 40 | fn next(&mut self) -> u64 { | ||
| 41 | self.0 = self | ||
| 42 | .0 | ||
| 43 | .wrapping_mul(6364136223846793005) | ||
| 44 | .wrapping_add(1442695040888963407); | ||
| 45 | self.0 >> 33 | ||
| 46 | } | ||
| 47 | |||
| 48 | fn coin(&mut self) -> bool { | ||
| 49 | self.next().is_multiple_of(2) | ||
| 50 | } | ||
| 51 | |||
| 52 | fn pick(&mut self, count: usize) -> Option<usize> { | ||
| 53 | (count > 0).then(|| self.next() as usize % count) | ||
| 54 | } | ||
| 55 | |||
| 56 | fn text(&mut self) -> String { | ||
| 57 | let words = ["", "a", "Two words", "東京 🦀", "é\u{301}", "longer text here", " "]; | ||
| 58 | words[self.next() as usize % words.len()].to_owned() | ||
| 59 | } | ||
| 60 | } | ||
| 61 | |||
| 62 | pub(super) fn seeded<T>(seed: u64, f: impl FnOnce() -> T) -> T { | ||
| 63 | let outer = GUIDS.replace(Some(seed)); | ||
| 64 | let result = f(); | ||
| 65 | GUIDS.set(outer); | ||
| 66 | result | ||
| 67 | } | ||
| 68 | |||
| 69 | fn pages(image: &[u8]) -> Vec<ExGuid> { | ||
| 70 | let store = Store::parse(image).unwrap(); | ||
| 71 | let index = RevisionIndex::parse(&store).unwrap(); | ||
| 72 | let document = Document::parse(&index).unwrap(); | ||
| 73 | document | ||
| 74 | .pages() | ||
| 75 | .unwrap() | ||
| 76 | .into_iter() | ||
| 77 | .map(|(space, _)| space) | ||
| 78 | .collect() | ||
| 79 | } | ||
| 80 | |||
| 81 | fn read(image: &[u8], space: ExGuid) -> Page { | ||
| 82 | let store = Store::parse(image).unwrap(); | ||
| 83 | let index = RevisionIndex::parse(&store).unwrap(); | ||
| 84 | Page::from_space(&Document::parse(&index).unwrap(), space).unwrap() | ||
| 85 | } | ||
| 86 | |||
| 87 | /// `page` with writer-chosen identities replaced in document order: list nodes, which a | ||
| 88 | /// copy for a second owner gets, and tag field indices. | ||
| 89 | pub(super) fn normalize(page: &Page) -> Page { | ||
| 90 | let mut page = page.clone(); | ||
| 91 | let mut lists = BTreeMap::new(); | ||
| 92 | let mut paths: Vec<_> = model::lists(&page).into_iter().map(|(path, _, _)| path).collect(); | ||
| 93 | paths.sort_by_key(|path| format!("{path:?}")); | ||
| 94 | for path in &paths { | ||
| 95 | for paragraph in model::list_at(&mut page, path).iter_mut() { | ||
| 96 | for list in &mut paragraph.lists { | ||
| 97 | let next = ExGuid { | ||
| 98 | guid: [0xee; 16], | ||
| 99 | n: 1 + lists.len() as u32, | ||
| 100 | }; | ||
| 101 | *list = *lists.entry(*list).or_insert(next); | ||
| 102 | } | ||
| 103 | for tag in paragraph.tags.iter_mut() { | ||
| 104 | tag.extra_set = 0; | ||
| 105 | } | ||
| 106 | if let Some(text) = paragraph.text_mut() { | ||
| 107 | for tag in &mut text.tags { | ||
| 108 | tag.extra_set = 0; | ||
| 109 | } | ||
| 110 | // An absent paragraph value and its default are the same formatting. | ||
| 111 | let mut previous = 0; | ||
| 112 | let runs: Vec<(String, Format)> = text | ||
| 113 | .text | ||
| 114 | .spans() | ||
| 115 | .iter() | ||
| 116 | .map(|span| { | ||
| 117 | let mut format = span.format.clone(); | ||
| 118 | format.alignment = format.alignment.filter(|v| *v != 0); | ||
| 119 | format.rtl = format.rtl.filter(|v| *v); | ||
| 120 | format.space_before = format.space_before.filter(|v| *v != 0.0); | ||
| 121 | format.space_after = format.space_after.filter(|v| *v != 0.0); | ||
| 122 | format.line_spacing = format.line_spacing.filter(|v| *v != 0.0); | ||
| 123 | let run = (text.text.text()[previous..span.end].to_owned(), format); | ||
| 124 | previous = span.end; | ||
| 125 | run | ||
| 126 | }) | ||
| 127 | .collect(); | ||
| 128 | text.text = Paragraph::from_runs(runs); | ||
| 129 | } | ||
| 130 | } | ||
| 131 | } | ||
| 132 | page.definitions = std::mem::take(&mut page.definitions) | ||
| 133 | .into_iter() | ||
| 134 | .filter_map(|(id, definition)| match definition.kind { | ||
| 135 | Kind::List { .. } => lists.get(&id).map(|id| (*id, definition)), | ||
| 136 | _ => Some((id, definition)), | ||
| 137 | }) | ||
| 138 | .collect(); | ||
| 139 | page | ||
| 140 | } | ||
| 141 | |||
| 142 | #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] | ||
| 143 | pub(super) enum Family { | ||
| 144 | Text, | ||
| 145 | Format, | ||
| 146 | Insert, | ||
| 147 | Paste, | ||
| 148 | Delete, | ||
| 149 | Move, | ||
| 150 | Collapse, | ||
| 151 | Position, | ||
| 152 | Width, | ||
| 153 | NewOutline, | ||
| 154 | List, | ||
| 155 | Tag, | ||
| 156 | Split, | ||
| 157 | Join, | ||
| 158 | Indent, | ||
| 159 | Outdent, | ||
| 160 | Alignment, | ||
| 161 | Style, | ||
| 162 | TableRow, | ||
| 163 | TableColumn, | ||
| 164 | TableDelete, | ||
| 165 | TableWidth, | ||
| 166 | TableShading, | ||
| 167 | NewTable, | ||
| 168 | PagePicture, | ||
| 169 | PictureEdit, | ||
| 170 | ParagraphPicture, | ||
| 171 | Attachment, | ||
| 172 | AttachmentRename, | ||
| 173 | NewInk, | ||
| 174 | InkStrokes, | ||
| 175 | Link, | ||
| 176 | Equation, | ||
| 177 | } | ||
| 178 | |||
| 179 | pub(super) const FAMILIES: [Family; 33] = [ | ||
| 180 | Family::Text, | ||
| 181 | Family::Format, | ||
| 182 | Family::Insert, | ||
| 183 | Family::Paste, | ||
| 184 | Family::Delete, | ||
| 185 | Family::Move, | ||
| 186 | Family::Collapse, | ||
| 187 | Family::Position, | ||
| 188 | Family::Width, | ||
| 189 | Family::NewOutline, | ||
| 190 | Family::List, | ||
| 191 | Family::Tag, | ||
| 192 | Family::Split, | ||
| 193 | Family::Join, | ||
| 194 | Family::Indent, | ||
| 195 | Family::Outdent, | ||
| 196 | Family::Alignment, | ||
| 197 | Family::Style, | ||
| 198 | Family::TableRow, | ||
| 199 | Family::TableColumn, | ||
| 200 | Family::TableDelete, | ||
| 201 | Family::TableWidth, | ||
| 202 | Family::TableShading, | ||
| 203 | Family::NewTable, | ||
| 204 | Family::PagePicture, | ||
| 205 | Family::PictureEdit, | ||
| 206 | Family::ParagraphPicture, | ||
| 207 | Family::Attachment, | ||
| 208 | Family::AttachmentRename, | ||
| 209 | Family::NewInk, | ||
| 210 | Family::InkStrokes, | ||
| 211 | Family::Link, | ||
| 212 | Family::Equation, | ||
| 213 | ]; | ||
| 214 | |||
| 215 | const PNG: &[u8] = &[ | ||
| 216 | 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, | ||
| 217 | 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x06, 0x00, 0x00, 0x00, 0x1f, | ||
| 218 | 0x15, 0xc4, 0x89, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x44, 0x41, 0x54, 0x78, 0x9c, 0x63, 0xf8, | ||
| 219 | 0xcf, 0xc0, 0xf0, 0x1f, 0x00, 0x05, 0x00, 0x01, 0xff, 0x89, 0x99, 0x3d, 0x1d, 0x00, 0x00, | ||
| 220 | 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82, | ||
| 221 | ]; | ||
| 222 | |||
| 223 | fn id() -> ExGuid { | ||
| 224 | new_id().unwrap() | ||
| 225 | } | ||
| 226 | |||
| 227 | fn text_paragraph(text: &str, format: Format) -> PageParagraph { | ||
| 228 | PageParagraph { | ||
| 229 | id: id(), | ||
| 230 | parent: None, | ||
| 231 | level: 1, | ||
| 232 | style: None, | ||
| 233 | format: Format::default(), | ||
| 234 | content: ParagraphContent::Text(TextObject { | ||
| 235 | id: id(), | ||
| 236 | date_field: None, | ||
| 237 | text: Paragraph::new(text.to_owned(), format), | ||
| 238 | tags: Vec::new(), | ||
| 239 | }), | ||
| 240 | lists: Vec::new(), | ||
| 241 | tags: Vec::new(), | ||
| 242 | media: Default::default(), | ||
| 243 | collapsed: false, | ||
| 244 | } | ||
| 245 | } | ||
| 246 | |||
| 247 | /// Paths of the page's editable lists: ordinary outlines and their cells. | ||
| 248 | fn editable(page: &Page) -> Vec<model::Path> { | ||
| 249 | let titles: Vec<ExGuid> = page | ||
| 250 | .objects | ||
| 251 | .iter() | ||
| 252 | .filter_map(|o| match o { | ||
| 253 | PageObject::Title(title) => Some(title.outlines.iter().map(|o| o.id).collect::<Vec<_>>()), | ||
| 254 | _ => None, | ||
| 255 | }) | ||
| 256 | .flatten() | ||
| 257 | .collect(); | ||
| 258 | model::lists(page) | ||
| 259 | .into_iter() | ||
| 260 | .filter(|(_, owner, list)| !titles.contains(owner) && !list.is_empty()) | ||
| 261 | .map(|(path, _, _)| path) | ||
| 262 | .collect() | ||
| 263 | } | ||
| 264 | |||
| 265 | /// A text paragraph of an editable list: its list and index. | ||
| 266 | fn pick_text(page: &Page, rng: &mut Rng) -> Option<(model::Path, usize)> { | ||
| 267 | let candidates: Vec<(model::Path, usize)> = editable(page) | ||
| 268 | .into_iter() | ||
| 269 | .flat_map(|path| { | ||
| 270 | let list = model::lists(page) | ||
| 271 | .into_iter() | ||
| 272 | .find(|(p, _, _)| format!("{p:?}") == format!("{path:?}")) | ||
| 273 | .unwrap() | ||
| 274 | .2 | ||
| 275 | .clone(); | ||
| 276 | list.iter() | ||
| 277 | .enumerate() | ||
| 278 | .filter(|(_, p)| { | ||
| 279 | p.text().is_some_and(|t| { | ||
| 280 | t.date_field.is_none() && !crate::page::Math::is_equation(&t.text) | ||
| 281 | }) | ||
| 282 | }) | ||
| 283 | .map(|(i, _)| (path.clone(), i)) | ||
| 284 | .collect::<Vec<_>>() | ||
| 285 | }) | ||
| 286 | .collect(); | ||
| 287 | candidates.get(rng.pick(candidates.len())?).cloned() | ||
| 288 | } | ||
| 289 | |||
| 290 | fn outlines(page: &mut Page) -> Vec<&mut Outline> { | ||
| 291 | page.objects | ||
| 292 | .iter_mut() | ||
| 293 | .filter_map(|o| match o { | ||
| 294 | PageObject::Outline(outline) => Some(outline), | ||
| 295 | _ => None, | ||
| 296 | }) | ||
| 297 | .collect() | ||
| 298 | } | ||
| 299 | |||
| 300 | fn subtree_end(list: &[PageParagraph], index: usize) -> usize { | ||
| 301 | let mut members = vec![list[index].id]; | ||
| 302 | let mut end = index + 1; | ||
| 303 | while end < list.len() && list[end].parent.is_some_and(|p| members.contains(&p)) { | ||
| 304 | members.push(list[end].id); | ||
| 305 | end += 1; | ||
| 306 | } | ||
| 307 | end | ||
| 308 | } | ||
| 309 | |||
| 310 | fn tables(page: &mut Page) -> Vec<&mut Table> { | ||
| 311 | let mut out = Vec::new(); | ||
| 312 | for outline in outlines(page) { | ||
| 313 | for paragraph in &mut outline.paragraphs { | ||
| 314 | if let ParagraphContent::Table(table) = &mut paragraph.content { | ||
| 315 | out.push(table); | ||
| 316 | } | ||
| 317 | } | ||
| 318 | } | ||
| 319 | out | ||
| 320 | } | ||
| 321 | |||
| 322 | fn new_cell(indents: &[f32]) -> TableCell { | ||
| 323 | TableCell { | ||
| 324 | id: id(), | ||
| 325 | layout: Layout::default(), | ||
| 326 | indents: indents.to_vec(), | ||
| 327 | shading: None, | ||
| 328 | paragraphs: vec![text_paragraph("cell", Format::default())], | ||
| 329 | unsupported: Vec::new(), | ||
| 330 | } | ||
| 331 | } | ||
| 332 | |||
| 333 | /// Applies one random edit of `family` to `page`; false when the page offers none. | ||
| 334 | pub(super) fn mutate(page: &mut Page, rng: &mut Rng, family: Family) -> bool { | ||
| 335 | match family { | ||
| 336 | Family::Text | Family::Format | Family::Link => { | ||
| 337 | let Some((path, index)) = pick_text(page, rng) else { | ||
| 338 | return false; | ||
| 339 | }; | ||
| 340 | let text = &mut model::list_at(page, &path)[index].text_mut().unwrap().text; | ||
| 341 | let end = text.utf16_offset(text.text().len()).unwrap(); | ||
| 342 | let chars: Vec<u32> = text | ||
| 343 | .text() | ||
| 344 | .char_indices() | ||
| 345 | .map(|(i, _)| text.utf16_offset(i).unwrap()) | ||
| 346 | .chain([end]) | ||
| 347 | .collect(); | ||
| 348 | let a = chars[rng.pick(chars.len()).unwrap()]; | ||
| 349 | let b = chars[rng.pick(chars.len()).unwrap()]; | ||
| 350 | let range = a.min(b)..a.max(b); | ||
| 351 | match family { | ||
| 352 | Family::Text => { | ||
| 353 | let format = text.format_at(range.start).unwrap().clone(); | ||
| 354 | text.apply(crate::page::text::Edit { | ||
| 355 | range, | ||
| 356 | replacement: Paragraph::new(rng.text(), format), | ||
| 357 | }) | ||
| 358 | .is_ok() | ||
| 359 | } | ||
| 360 | Family::Format => { | ||
| 361 | if range.is_empty() { | ||
| 362 | return false; | ||
| 363 | } | ||
| 364 | let slice = text.slice(range.clone()).unwrap(); | ||
| 365 | let attribute = rng.next() % 6; | ||
| 366 | let mut from = 0; | ||
| 367 | let runs: Vec<(String, Format)> = slice | ||
| 368 | .spans() | ||
| 369 | .iter() | ||
| 370 | .map(|span| { | ||
| 371 | let mut format = span.format.clone(); | ||
| 372 | match attribute { | ||
| 373 | 0 => format.bold = Some(!format.bold.unwrap_or(false)), | ||
| 374 | 1 => format.italic = Some(!format.italic.unwrap_or(false)), | ||
| 375 | 2 => format.underline = Some(!format.underline.unwrap_or(false)), | ||
| 376 | 3 => format.font_size = Some(if format.font_size == Some(14.0) { 11.0 } else { 14.0 }), | ||
| 377 | 4 => format.color = Some(0x00ff), | ||
| 378 | _ => format.font = Some("Consolas".into()), | ||
| 379 | } | ||
| 380 | let run = (slice.text()[from..span.end].to_owned(), format); | ||
| 381 | from = span.end; | ||
| 382 | run | ||
| 383 | }) | ||
| 384 | .collect(); | ||
| 385 | text.apply(crate::page::text::Edit { | ||
| 386 | range, | ||
| 387 | replacement: Paragraph::from_runs(runs), | ||
| 388 | }) | ||
| 389 | .is_ok() | ||
| 390 | } | ||
| 391 | _ => { | ||
| 392 | if range.is_empty() | ||
| 393 | || text.spans().iter().any(|s| s.format.hyperlink == Some(true)) | ||
| 394 | { | ||
| 395 | return false; | ||
| 396 | } | ||
| 397 | let base = text.format_at(range.start).unwrap().clone(); | ||
| 398 | let label = text.slice(range.clone()).unwrap(); | ||
| 399 | let mut code = base.clone(); | ||
| 400 | code.hyperlink = Some(true); | ||
| 401 | code.hyperlink_label = Some(true); | ||
| 402 | code.hidden = Some(true); | ||
| 403 | let mut from = 0; | ||
| 404 | let mut runs = vec![( | ||
| 405 | "\u{fddf}HYPERLINK \"https://example.invalid/op\"".to_owned(), | ||
| 406 | code, | ||
| 407 | )]; | ||
| 408 | for span in label.spans() { | ||
| 409 | let mut format = span.format.clone(); | ||
| 410 | format.hyperlink = Some(true); | ||
| 411 | format.hyperlink_label = Some(true); | ||
| 412 | runs.push((label.text()[from..span.end].to_owned(), format)); | ||
| 413 | from = span.end; | ||
| 414 | } | ||
| 415 | text.apply(crate::page::text::Edit { | ||
| 416 | range, | ||
| 417 | replacement: Paragraph::from_runs(runs), | ||
| 418 | }) | ||
| 419 | .is_ok() | ||
| 420 | } | ||
| 421 | } | ||
| 422 | } | ||
| 423 | Family::Insert | Family::Paste | Family::Style => { | ||
| 424 | let paths = editable(page); | ||
| 425 | let Some(path) = paths.get(rng.pick(paths.len()).unwrap_or(0)).cloned() else { | ||
| 426 | return false; | ||
| 427 | }; | ||
| 428 | let style = (family == Family::Style).then(|| { | ||
| 429 | let existing = page.definitions.iter().find_map(|(id, d)| { | ||
| 430 | matches!(&d.kind, Kind::Style { name: Some(name) } if name == "Heading 2") | ||
| 431 | .then_some(*id) | ||
| 432 | }); | ||
| 433 | existing.unwrap_or_else(|| { | ||
| 434 | let id = id(); | ||
| 435 | page.definitions.insert( | ||
| 436 | id, | ||
| 437 | Definition { | ||
| 438 | kind: Kind::Style { | ||
| 439 | name: Some("Heading 2".into()), | ||
| 440 | }, | ||
| 441 | format: Format { | ||
| 442 | font: Some("Calibri".into()), | ||
| 443 | font_size: Some(13.0), | ||
| 444 | bold: Some(true), | ||
| 445 | color: Some(0x7d4f1e), | ||
| 446 | ..Default::default() | ||
| 447 | }, | ||
| 448 | }, | ||
| 449 | ); | ||
| 450 | id | ||
| 451 | }) | ||
| 452 | }); | ||
| 453 | let list = model::list_at(page, &path); | ||
| 454 | let template = list | ||
| 455 | .iter() | ||
| 456 | .find_map(|p| p.text().map(|t| t.text.format_at(0).unwrap().clone())) | ||
| 457 | .unwrap_or_default(); | ||
| 458 | let boundaries: Vec<usize> = (0..=list.len()) | ||
| 459 | .filter(|i| list.get(*i).is_none_or(|p| p.parent.is_none())) | ||
| 460 | .collect(); | ||
| 461 | let at = boundaries[rng.pick(boundaries.len()).unwrap()]; | ||
| 462 | let count = if family == Family::Paste { 2 + rng.next() as usize % 3 } else { 1 }; | ||
| 463 | let mut inserted = Vec::new(); | ||
| 464 | for i in 0..count { | ||
| 465 | let mut paragraph = text_paragraph(&rng.text(), template.clone()); | ||
| 466 | if let Some(style) = style { | ||
| 467 | paragraph.style = Some(style); | ||
| 468 | } | ||
| 469 | if i > 0 && rng.coin() { | ||
| 470 | let parent: &PageParagraph = &inserted[0]; | ||
| 471 | paragraph.parent = Some(parent.id); | ||
| 472 | paragraph.level = parent.level + 1; | ||
| 473 | } | ||
| 474 | inserted.push(paragraph); | ||
| 475 | } | ||
| 476 | // Children follow their parent; keep them after the first inserted paragraph. | ||
| 477 | inserted.sort_by_key(|p| p.parent.is_some()); | ||
| 478 | let cell_level = list.first().map_or(1, |p| p.level); | ||
| 479 | for paragraph in &mut inserted { | ||
| 480 | if paragraph.parent.is_none() { | ||
| 481 | paragraph.level = cell_level; | ||
| 482 | } | ||
| 483 | } | ||
| 484 | list.splice(at..at, inserted); | ||
| 485 | true | ||
| 486 | } | ||
| 487 | Family::Delete | Family::Move | Family::Collapse => { | ||
| 488 | let paths = editable(page); | ||
| 489 | let Some(path) = paths.get(rng.pick(paths.len()).unwrap_or(0)).cloned() else { | ||
| 490 | return false; | ||
| 491 | }; | ||
| 492 | let list = model::list_at(page, &path); | ||
| 493 | let top: Vec<usize> = (0..list.len()).filter(|i| list[*i].parent.is_none()).collect(); | ||
| 494 | match family { | ||
| 495 | Family::Delete => { | ||
| 496 | if top.len() < 2 { | ||
| 497 | return false; | ||
| 498 | } | ||
| 499 | let at = top[rng.pick(top.len()).unwrap()]; | ||
| 500 | let end = subtree_end(list, at); | ||
| 501 | list.drain(at..end); | ||
| 502 | true | ||
| 503 | } | ||
| 504 | Family::Move => { | ||
| 505 | if top.len() < 2 { | ||
| 506 | return false; | ||
| 507 | } | ||
| 508 | let from = top[rng.pick(top.len()).unwrap()]; | ||
| 509 | let end = subtree_end(list, from); | ||
| 510 | let block: Vec<_> = list.drain(from..end).collect(); | ||
| 511 | let boundaries: Vec<usize> = (0..=list.len()) | ||
| 512 | .filter(|i| list.get(*i).is_none_or(|p| p.parent.is_none())) | ||
| 513 | .collect(); | ||
| 514 | let to = boundaries[rng.pick(boundaries.len()).unwrap()]; | ||
| 515 | list.splice(to..to, block); | ||
| 516 | true | ||
| 517 | } | ||
| 518 | _ => { | ||
| 519 | let at = rng.pick(list.len()).unwrap(); | ||
| 520 | list[at].collapsed ^= true; | ||
| 521 | true | ||
| 522 | } | ||
| 523 | } | ||
| 524 | } | ||
| 525 | Family::Position | Family::Width => { | ||
| 526 | let mut outlines: Vec<&mut Outline> = outlines(page).into_iter().filter(|o| !o.title).collect(); | ||
| 527 | let Some(at) = rng.pick(outlines.len()) else { | ||
| 528 | return false; | ||
| 529 | }; | ||
| 530 | let outline = &mut outlines[at]; | ||
| 531 | if family == Family::Position { | ||
| 532 | outline.layout.x = Some((rng.next() % 200) as f32 * 1.5); | ||
| 533 | outline.layout.y = Some((rng.next() % 200) as f32 * 2.25); | ||
| 534 | } else { | ||
| 535 | outline.layout.max_width = Some(36.0 + (rng.next() % 200) as f32 * 2.0); | ||
| 536 | outline.layout.width_set_by_user = Some(rng.coin()); | ||
| 537 | } | ||
| 538 | true | ||
| 539 | } | ||
| 540 | Family::NewOutline => { | ||
| 541 | let at = page | ||
| 542 | .objects | ||
| 543 | .iter() | ||
| 544 | .position(|o| matches!(o, PageObject::Title(_))) | ||
| 545 | .unwrap_or(page.objects.len()); | ||
| 546 | let at = rng.pick(at + 1).unwrap(); | ||
| 547 | page.objects.insert( | ||
| 548 | at, | ||
| 549 | PageObject::Outline(Outline { | ||
| 550 | id: id(), | ||
| 551 | title: false, | ||
| 552 | min_width: None, | ||
| 553 | layout: Layout { | ||
| 554 | x: Some((rng.next() % 100) as f32 * 1.5), | ||
| 555 | y: Some((rng.next() % 100) as f32 * 2.25), | ||
| 556 | ..Default::default() | ||
| 557 | }, | ||
| 558 | indents: Vec::new(), | ||
| 559 | paragraphs: vec![text_paragraph(&rng.text(), Format::default())], | ||
| 560 | unsupported: Vec::new(), | ||
| 561 | }), | ||
| 562 | ); | ||
| 563 | true | ||
| 564 | } | ||
| 565 | Family::List | Family::Tag | Family::Alignment => { | ||
| 566 | let Some((path, index)) = pick_text(page, rng) else { | ||
| 567 | return false; | ||
| 568 | }; | ||
| 569 | match family { | ||
| 570 | Family::List => { | ||
| 571 | let paragraph = &model::list_at(page, &path)[index]; | ||
| 572 | if paragraph.lists.is_empty() { | ||
| 573 | let list = id(); | ||
| 574 | page.definitions.insert( | ||
| 575 | list, | ||
| 576 | Definition { | ||
| 577 | kind: Kind::List { | ||
| 578 | font: Some("Courier New".into()), | ||
| 579 | format: Some("\u{25cb}".into()), | ||
| 580 | restart: None, | ||
| 581 | bullet: Some(4), | ||
| 582 | }, | ||
| 583 | format: Format { | ||
| 584 | font_size: Some(11.0), | ||
| 585 | color: Some(0xff000000), | ||
| 586 | ..Default::default() | ||
| 587 | }, | ||
| 588 | }, | ||
| 589 | ); | ||
| 590 | model::list_at(page, &path)[index].lists = vec![list]; | ||
| 591 | } else { | ||
| 592 | model::list_at(page, &path)[index].lists.clear(); | ||
| 593 | } | ||
| 594 | } | ||
| 595 | Family::Tag => { | ||
| 596 | let definition = page | ||
| 597 | .definitions | ||
| 598 | .iter() | ||
| 599 | .find(|(_, d)| matches!(&d.kind, Kind::TagDefinition { label: Some(l), .. } if l == "Op task")) | ||
| 600 | .map(|(id, _)| *id) | ||
| 601 | .unwrap_or_else(|| { | ||
| 602 | let id = id(); | ||
| 603 | page.definitions.insert( | ||
| 604 | id, | ||
| 605 | Definition { | ||
| 606 | kind: Kind::TagDefinition { | ||
| 607 | label: Some("Op task".into()), | ||
| 608 | action_type: Some(0), | ||
| 609 | shape: Some(3), | ||
| 610 | color: None, | ||
| 611 | highlight: None, | ||
| 612 | }, | ||
| 613 | format: Default::default(), | ||
| 614 | }, | ||
| 615 | ); | ||
| 616 | id | ||
| 617 | }); | ||
| 618 | let status = (rng.next() % 2) as u16; | ||
| 619 | let text = model::list_at(page, &path)[index].text_mut().unwrap(); | ||
| 620 | if text.tags.is_empty() { | ||
| 621 | text.tags.push(crate::document::Tag { | ||
| 622 | definition: Some(definition), | ||
| 623 | action_type: None, | ||
| 624 | status, | ||
| 625 | created: Some(1_262_401_445), | ||
| 626 | completed: (status == 1).then_some(1_262_402_000), | ||
| 627 | start: None, | ||
| 628 | due: None, | ||
| 629 | task_id: None, | ||
| 630 | extra_set: 0, | ||
| 631 | }); | ||
| 632 | } else { | ||
| 633 | text.tags.clear(); | ||
| 634 | } | ||
| 635 | } | ||
| 636 | _ => { | ||
| 637 | let alignment = (rng.next() % 3) as u8; | ||
| 638 | let text = &mut model::list_at(page, &path)[index].text_mut().unwrap().text; | ||
| 639 | let mut from = 0; | ||
| 640 | let runs: Vec<(String, Format)> = text | ||
| 641 | .spans() | ||
| 642 | .iter() | ||
| 643 | .map(|span| { | ||
| 644 | let mut format = span.format.clone(); | ||
| 645 | format.alignment = Some(alignment); | ||
| 646 | let run = (text.text()[from..span.end].to_owned(), format); | ||
| 647 | from = span.end; | ||
| 648 | run | ||
| 649 | }) | ||
| 650 | .collect(); | ||
| 651 | *text = Paragraph::from_runs(runs); | ||
| 652 | } | ||
| 653 | } | ||
| 654 | true | ||
| 655 | } | ||
| 656 | Family::Split | Family::Join => { | ||
| 657 | let Some((path, index)) = pick_text(page, rng) else { | ||
| 658 | return false; | ||
| 659 | }; | ||
| 660 | let list = model::list_at(page, &path); | ||
| 661 | if family == Family::Split { | ||
| 662 | let paragraph = &list[index]; | ||
| 663 | let text = &paragraph.text().unwrap().text; | ||
| 664 | let end = text.utf16_offset(text.text().len()).unwrap(); | ||
| 665 | if text.spans().iter().any(|s| s.format.hyperlink == Some(true)) { | ||
| 666 | return false; | ||
| 667 | } | ||
| 668 | let offsets: Vec<u32> = text | ||
| 669 | .text() | ||
| 670 | .char_indices() | ||
| 671 | .map(|(i, _)| text.utf16_offset(i).unwrap()) | ||
| 672 | .chain([end]) | ||
| 673 | .collect(); | ||
| 674 | let at = offsets[rng.pick(offsets.len()).unwrap()]; | ||
| 675 | let (head, tail) = (text.slice(0..at).unwrap(), text.slice(at..end).unwrap()); | ||
| 676 | let mut right = paragraph.clone(); | ||
| 677 | right.id = id(); | ||
| 678 | right.tags.clear(); | ||
| 679 | right.media = Default::default(); | ||
| 680 | right.content = ParagraphContent::Text(TextObject { | ||
| 681 | id: id(), | ||
| 682 | date_field: None, | ||
| 683 | text: tail, | ||
| 684 | tags: Vec::new(), | ||
| 685 | }); | ||
| 686 | let left_id = paragraph.id; | ||
| 687 | list[index].text_mut().unwrap().text = head; | ||
| 688 | for child in &mut list[index + 1..] { | ||
| 689 | if child.parent == Some(left_id) { | ||
| 690 | child.parent = Some(right.id); | ||
| 691 | } | ||
| 692 | } | ||
| 693 | list.insert(index + 1, right); | ||
| 694 | true | ||
| 695 | } else { | ||
| 696 | let next = index + 1; | ||
| 697 | let Some(lower) = list.get(next) else { | ||
| 698 | return false; | ||
| 699 | }; | ||
| 700 | let upper = &list[index]; | ||
| 701 | if lower.parent != upper.parent | ||
| 702 | || lower.level != upper.level | ||
| 703 | || list.iter().any(|p| p.parent == Some(upper.id) || p.parent == Some(lower.id)) | ||
| 704 | { | ||
| 705 | return false; | ||
| 706 | } | ||
| 707 | let Some(lower_text) = lower.text().cloned() else { | ||
| 708 | return false; | ||
| 709 | }; | ||
| 710 | if lower_text.text.text().is_empty() || crate::page::Math::is_equation(&lower_text.text) { | ||
| 711 | return false; | ||
| 712 | } | ||
| 713 | let upper = list[index].text_mut().unwrap(); | ||
| 714 | if upper.text.text().is_empty() { | ||
| 715 | upper.id = lower_text.id; | ||
| 716 | upper.text = lower_text.text; | ||
| 717 | } else { | ||
| 718 | upper.text.append(lower_text.text).unwrap(); | ||
| 719 | } | ||
| 720 | list.remove(next); | ||
| 721 | true | ||
| 722 | } | ||
| 723 | } | ||
| 724 | Family::Indent | Family::Outdent => { | ||
| 725 | let paths = editable(page); | ||
| 726 | let Some(path) = paths.get(rng.pick(paths.len()).unwrap_or(0)).cloned() else { | ||
| 727 | return false; | ||
| 728 | }; | ||
| 729 | if !format!("{path:?}").contains("cells: []") { | ||
| 730 | return false; | ||
| 731 | } | ||
| 732 | let list = model::list_at(page, &path); | ||
| 733 | let Some(index) = rng.pick(list.len()) else { | ||
| 734 | return false; | ||
| 735 | }; | ||
| 736 | let end = subtree_end(list, index); | ||
| 737 | if family == Family::Indent { | ||
| 738 | // Tab: the paragraph becomes the last child of its previous sibling. | ||
| 739 | let parent = list[index].parent; | ||
| 740 | let Some(sibling) = list[..index].iter().rev().find(|p| p.parent == parent).map(|p| (p.id, p.level)) else { | ||
| 741 | return false; | ||
| 742 | }; | ||
| 743 | list[index].parent = Some(sibling.0); | ||
| 744 | let delta = sibling.1 + 1 - list[index].level; | ||
| 745 | if delta > 1 || list[index].level + 1 > 31 { | ||
| 746 | return false; | ||
| 747 | } | ||
| 748 | for paragraph in &mut list[index..end] { | ||
| 749 | paragraph.level += 1; | ||
| 750 | } | ||
| 751 | list[index].level = list[index].level.max(sibling.1 + 1); | ||
| 752 | } else { | ||
| 753 | let Some(parent) = list[index].parent else { | ||
| 754 | return false; | ||
| 755 | }; | ||
| 756 | // Shift-Tab of a last child: it follows its parent. | ||
| 757 | if list[end..].iter().any(|p| p.parent == Some(parent)) { | ||
| 758 | return false; | ||
| 759 | } | ||
| 760 | let grandparent = list.iter().find(|p| p.id == parent).unwrap().parent; | ||
| 761 | list[index].parent = grandparent; | ||
| 762 | for paragraph in &mut list[index..end] { | ||
| 763 | paragraph.level -= 1; | ||
| 764 | } | ||
| 765 | if list[index].level == 0 { | ||
| 766 | return false; | ||
| 767 | } | ||
| 768 | } | ||
| 769 | true | ||
| 770 | } | ||
| 771 | Family::TableRow | ||
| 772 | | Family::TableColumn | ||
| 773 | | Family::TableDelete | ||
| 774 | | Family::TableWidth | ||
| 775 | | Family::TableShading => { | ||
| 776 | let mut tables = tables(page); | ||
| 777 | let Some(at) = rng.pick(tables.len()) else { | ||
| 778 | return false; | ||
| 779 | }; | ||
| 780 | let table = &mut tables[at]; | ||
| 781 | let indents = table.rows[0].cells[0].indents.clone(); | ||
| 782 | match family { | ||
| 783 | Family::TableRow => { | ||
| 784 | let row = TableRow { | ||
| 785 | id: id(), | ||
| 786 | cells: table.columns.iter().map(|_| new_cell(&indents)).collect(), | ||
| 787 | }; | ||
| 788 | let at = rng.pick(table.rows.len() + 1).unwrap(); | ||
| 789 | table.rows.insert(at, row); | ||
| 790 | } | ||
| 791 | Family::TableColumn => { | ||
| 792 | let at = rng.pick(table.columns.len() + 1).unwrap(); | ||
| 793 | table.columns.insert(at, TableColumn { width: 72.0, locked: false }); | ||
| 794 | for row in &mut table.rows { | ||
| 795 | row.cells.insert(at, new_cell(&indents)); | ||
| 796 | } | ||
| 797 | } | ||
| 798 | Family::TableDelete => { | ||
| 799 | if rng.coin() && table.rows.len() > 1 { | ||
| 800 | let at = rng.pick(table.rows.len()).unwrap(); | ||
| 801 | table.rows.remove(at); | ||
| 802 | } else if table.columns.len() > 1 { | ||
| 803 | let at = rng.pick(table.columns.len()).unwrap(); | ||
| 804 | table.columns.remove(at); | ||
| 805 | for row in &mut table.rows { | ||
| 806 | row.cells.remove(at); | ||
| 807 | } | ||
| 808 | } else { | ||
| 809 | return false; | ||
| 810 | } | ||
| 811 | } | ||
| 812 | Family::TableWidth => { | ||
| 813 | let at = rng.pick(table.columns.len()).unwrap(); | ||
| 814 | table.columns[at].width = 40.0 + (rng.next() % 100) as f32; | ||
| 815 | table.columns[at].locked = rng.coin(); | ||
| 816 | } | ||
| 817 | _ => { | ||
| 818 | let cells: Vec<_> = table.rows.iter_mut().flat_map(|r| &mut r.cells).collect(); | ||
| 819 | let len = cells.len(); | ||
| 820 | let cell = cells.into_iter().nth(rng.pick(len).unwrap()).unwrap(); | ||
| 821 | cell.shading = if cell.shading.is_some() { None } else { Some(0x00ccff) }; | ||
| 822 | } | ||
| 823 | } | ||
| 824 | true | ||
| 825 | } | ||
| 826 | Family::NewTable | Family::ParagraphPicture | Family::Attachment | Family::Equation => { | ||
| 827 | let equation = page.objects.iter().find_map(|o| match o { | ||
| 828 | PageObject::Outline(outline) => outline.paragraphs.iter().find_map(|p| { | ||
| 829 | p.text().filter(|t| crate::page::Math::is_equation(&t.text)).map(|t| t.text.clone()) | ||
| 830 | }), | ||
| 831 | _ => None, | ||
| 832 | }); | ||
| 833 | let Some(outline) = outlines(page).into_iter().find(|o| !o.title) else { | ||
| 834 | return false; | ||
| 835 | }; | ||
| 836 | let mut paragraph = text_paragraph("", Format::default()); | ||
| 837 | paragraph.content = match family { | ||
| 838 | Family::NewTable => ParagraphContent::Table(Table { | ||
| 839 | id: id(), | ||
| 840 | columns: vec![TableColumn { width: 72.0, locked: false }; 2], | ||
| 841 | rows: (0..2) | ||
| 842 | .map(|_| TableRow { | ||
| 843 | id: id(), | ||
| 844 | cells: (0..2).map(|_| new_cell(&[])).collect(), | ||
| 845 | }) | ||
| 846 | .collect(), | ||
| 847 | borders: Some(true), | ||
| 848 | layout: Layout::default(), | ||
| 849 | tags: Vec::new(), | ||
| 850 | }), | ||
| 851 | Family::ParagraphPicture => ParagraphContent::Image(Image { | ||
| 852 | id: id(), | ||
| 853 | layout: Layout::default(), | ||
| 854 | size: Some([0.75, 0.75]), | ||
| 855 | bytes: Some(PNG.into()), | ||
| 856 | alt: Some("dot".into()), | ||
| 857 | background: false, | ||
| 858 | }), | ||
| 859 | Family::Attachment => ParagraphContent::Attachment(Attachment { | ||
| 860 | id: id(), | ||
| 861 | filename: "notes.txt".into(), | ||
| 862 | source_path: Some(r"C:\notes.txt".into()), | ||
| 863 | size: None, | ||
| 864 | bytes: Some(b"attached bytes".as_slice().into()), | ||
| 865 | preview: None, | ||
| 866 | recording: None, | ||
| 867 | }), | ||
| 868 | _ => { | ||
| 869 | let Some(equation) = equation else { | ||
| 870 | return false; | ||
| 871 | }; | ||
| 872 | ParagraphContent::Text(TextObject { | ||
| 873 | id: id(), | ||
| 874 | date_field: None, | ||
| 875 | text: equation, | ||
| 876 | tags: Vec::new(), | ||
| 877 | }) | ||
| 878 | } | ||
| 879 | }; | ||
| 880 | let boundaries: Vec<usize> = (0..=outline.paragraphs.len()) | ||
| 881 | .filter(|i| outline.paragraphs.get(*i).is_none_or(|p| p.parent.is_none())) | ||
| 882 | .collect(); | ||
| 883 | let at = boundaries[rng.pick(boundaries.len()).unwrap()]; | ||
| 884 | outline.paragraphs.insert(at, paragraph); | ||
| 885 | true | ||
| 886 | } | ||
| 887 | Family::PagePicture | Family::NewInk => { | ||
| 888 | let at = page | ||
| 889 | .objects | ||
| 890 | .iter() | ||
| 891 | .position(|o| matches!(o, PageObject::Title(_))) | ||
| 892 | .unwrap_or(page.objects.len()); | ||
| 893 | let object = if family == Family::PagePicture { | ||
| 894 | PageObject::Image(Image { | ||
| 895 | id: id(), | ||
| 896 | layout: Layout { | ||
| 897 | x: Some(100.0), | ||
| 898 | y: Some(200.0), | ||
| 899 | ..Default::default() | ||
| 900 | }, | ||
| 901 | size: Some([0.75, 0.75]), | ||
| 902 | bytes: Some(PNG.into()), | ||
| 903 | alt: None, | ||
| 904 | background: false, | ||
| 905 | }) | ||
| 906 | } else { | ||
| 907 | PageObject::Ink(Ink { | ||
| 908 | id: id(), | ||
| 909 | layout: Layout::default(), | ||
| 910 | strokes: vec![stroke(rng)], | ||
| 911 | groups: Vec::new(), | ||
| 912 | }) | ||
| 913 | }; | ||
| 914 | page.objects.insert(at, object); | ||
| 915 | true | ||
| 916 | } | ||
| 917 | Family::PictureEdit => { | ||
| 918 | let images: Vec<&mut Image> = page | ||
| 919 | .objects | ||
| 920 | .iter_mut() | ||
| 921 | .filter_map(|o| match o { | ||
| 922 | PageObject::Image(image) => Some(image), | ||
| 923 | _ => None, | ||
| 924 | }) | ||
| 925 | .collect(); | ||
| 926 | let len = images.len(); | ||
| 927 | let Some(image) = images.into_iter().nth(rng.pick(len).unwrap_or(0)) else { | ||
| 928 | return false; | ||
| 929 | }; | ||
| 930 | match rng.next() % 3 { | ||
| 931 | 0 => { | ||
| 932 | image.layout.x = Some((rng.next() % 300) as f32); | ||
| 933 | image.layout.y = Some((rng.next() % 300) as f32); | ||
| 934 | } | ||
| 935 | 1 => { | ||
| 936 | image.layout.max_width = Some(20.0 + (rng.next() % 100) as f32); | ||
| 937 | image.layout.max_height = Some(20.0 + (rng.next() % 100) as f32); | ||
| 938 | image.layout.width_set_by_user = Some(true); | ||
| 939 | } | ||
| 940 | _ => image.alt = Some(rng.text()), | ||
| 941 | } | ||
| 942 | true | ||
| 943 | } | ||
| 944 | Family::AttachmentRename => { | ||
| 945 | let mut found = false; | ||
| 946 | for outline in outlines(page) { | ||
| 947 | for paragraph in &mut outline.paragraphs { | ||
| 948 | if let ParagraphContent::Attachment(attachment) = &mut paragraph.content | ||
| 949 | && !found | ||
| 950 | && attachment.recording.is_none() | ||
| 951 | { | ||
| 952 | attachment.filename = format!("renamed {}.txt", rng.next() % 100); | ||
| 953 | found = true; | ||
| 954 | } | ||
| 955 | } | ||
| 956 | } | ||
| 957 | found | ||
| 958 | } | ||
| 959 | Family::InkStrokes => { | ||
| 960 | let inks: Vec<&mut Ink> = page | ||
| 961 | .objects | ||
| 962 | .iter_mut() | ||
| 963 | .filter_map(|o| match o { | ||
| 964 | PageObject::Ink(ink) if ink.groups.is_empty() => Some(ink), | ||
| 965 | _ => None, | ||
| 966 | }) | ||
| 967 | .collect(); | ||
| 968 | let len = inks.len(); | ||
| 969 | let Some(ink) = inks.into_iter().nth(rng.pick(len).unwrap_or(0)) else { | ||
| 970 | return false; | ||
| 971 | }; | ||
| 972 | if rng.coin() || ink.strokes.len() < 2 { | ||
| 973 | ink.strokes.push(stroke(rng)); | ||
| 974 | } else { | ||
| 975 | let at = rng.pick(ink.strokes.len()).unwrap(); | ||
| 976 | ink.strokes.remove(at); | ||
| 977 | } | ||
| 978 | true | ||
| 979 | } | ||
| 980 | } | ||
| 981 | } | ||
| 982 | |||
| 983 | fn stroke(rng: &mut Rng) -> InkStroke { | ||
| 984 | let x = (rng.next() % 300) as f32; | ||
| 985 | InkStroke { | ||
| 986 | id: id(), | ||
| 987 | points: vec![[x, 100.0], [x + 10.0, 110.0], [x + 20.0, 105.0]], | ||
| 988 | width: 1.5, | ||
| 989 | height: 1.5, | ||
| 990 | color: Some(0x0000ff), | ||
| 991 | transparency: None, | ||
| 992 | pen_tip: None, | ||
| 993 | } | ||
| 994 | } | ||
| 995 | |||
| 996 | /// How one differential case came out. | ||
| 997 | #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)] | ||
| 998 | pub(super) enum Outcome { | ||
| 999 | /// The edit made no change the writers store. | ||
| 1000 | Unchanged, | ||
| 1001 | /// Both refused the edit. | ||
| 1002 | Refused, | ||
| 1003 | /// The page writer refused; the ops were written. | ||
| 1004 | OnlyOps, | ||
| 1005 | /// Identical bytes. | ||
| 1006 | Bytes, | ||
| 1007 | /// Equal pages, both images valid, the op revision no larger. | ||
| 1008 | Model, | ||
| 1009 | /// The op revision declares more than the page writer's. | ||
| 1010 | Larger, | ||
| 1011 | Mismatch(String), | ||
| 1012 | } | ||
| 1013 | |||
| 1014 | /// Objects the space's active revision declares in `image`. | ||
| 1015 | pub(super) fn declared(image: &[u8], space: ExGuid) -> usize { | ||
| 1016 | let store = Store::parse(image).unwrap(); | ||
| 1017 | let index = RevisionIndex::parse(&store).unwrap(); | ||
| 1018 | let rid = index.active(space).unwrap(); | ||
| 1019 | index.spaces[&space].revisions[&rid] | ||
| 1020 | .nodes | ||
| 1021 | .iter() | ||
| 1022 | .filter_map(|node| match node.reference { | ||
| 1023 | Some(crate::Reference::NodeList(chunk)) if node.id == 0xb0 => Some(chunk), | ||
| 1024 | _ => None, | ||
| 1025 | }) | ||
| 1026 | .map(|chunk| { | ||
| 1027 | store | ||
| 1028 | .lists | ||
| 1029 | .values() | ||
| 1030 | .find(|list| list.fragments.first().is_some_and(|f| f.offset == chunk.offset)) | ||
| 1031 | .map_or(0, |list| { | ||
| 1032 | list.nodes | ||
| 1033 | .iter() | ||
| 1034 | .filter(|n| { | ||
| 1035 | matches!(n.id, 0x2d | 0x2e | 0x41 | 0x42 | 0xa4 | 0xa5 | 0xc4 | 0xc5 | 0x72 | 0x73) | ||
| 1036 | }) | ||
| 1037 | .count() | ||
| 1038 | }) | ||
| 1039 | }) | ||
| 1040 | .sum() | ||
| 1041 | } | ||
| 1042 | |||
| 1043 | /// Writes `after` over page `space` of `source` both ways and compares the images. | ||
| 1044 | pub(super) fn differential(source: &[u8], space: ExGuid, after: &Page, seed: u64) -> Outcome { | ||
| 1045 | let before = read(source, space); | ||
| 1046 | let oracle = seeded(seed, || { | ||
| 1047 | crate::create::at(AT, || crate::PreparedEdit::page(source, space, after, "Author")) | ||
| 1048 | }); | ||
| 1049 | let arena = Arena::default(); | ||
| 1050 | let mut section = Section::open(&arena, source.to_vec()).unwrap(); | ||
| 1051 | let ops = lower_page(&before, after); | ||
| 1052 | let written = ops.as_ref().ok().map(|ops| { | ||
| 1053 | seeded(seed, || { | ||
| 1054 | let edit = Edit { | ||
| 1055 | at: AT, | ||
| 1056 | ops: ops | ||
| 1057 | .iter() | ||
| 1058 | .map(|op| Op::Page { | ||
| 1059 | space, | ||
| 1060 | op: op.clone(), | ||
| 1061 | }) | ||
| 1062 | .collect(), | ||
| 1063 | }; | ||
| 1064 | section.apply("Author", &edit).map(|()| { | ||
| 1065 | let predicted = { | ||
| 1066 | let mut page = before.clone(); | ||
| 1067 | for op in ops { | ||
| 1068 | model::apply(&mut page, op).unwrap(); | ||
| 1069 | } | ||
| 1070 | page | ||
| 1071 | }; | ||
| 1072 | (section.seal().unwrap(), predicted) | ||
| 1073 | }) | ||
| 1074 | }) | ||
| 1075 | }); | ||
| 1076 | let image = section.image(); | ||
| 1077 | match (oracle, written) { | ||
| 1078 | (Err(_), None | Some(Err(_))) => Outcome::Refused, | ||
| 1079 | (Err(_), Some(Ok(_))) => Outcome::OnlyOps, | ||
| 1080 | (Ok(_), None) => Outcome::Mismatch(format!("lowering refused: {:?}", ops.unwrap_err())), | ||
| 1081 | (Ok(_), Some(Err(error))) => Outcome::Mismatch(format!("apply refused: {error:?} for {:?}", ops.unwrap())), | ||
| 1082 | (Ok(edit), Some(Ok((transaction, predicted)))) => { | ||
| 1083 | let stored = normalize(&read(&image, space)); | ||
| 1084 | if normalize(&predicted) != stored { | ||
| 1085 | return Outcome::Mismatch(format!( | ||
| 1086 | "the model predicts otherwise: {}", | ||
| 1087 | first_difference(&stored, &normalize(&predicted)) | ||
| 1088 | )); | ||
| 1089 | } | ||
| 1090 | let expected = edit.as_bytes(); | ||
| 1091 | if expected == source && transaction.is_none() { | ||
| 1092 | return Outcome::Unchanged; | ||
| 1093 | } | ||
| 1094 | if expected == image.as_slice() { | ||
| 1095 | return Outcome::Bytes; | ||
| 1096 | } | ||
| 1097 | let store = Store::parse(&image).unwrap(); | ||
| 1098 | let index = RevisionIndex::parse(&store).unwrap(); | ||
| 1099 | if let Err(error) = index.validate_current() { | ||
| 1100 | return Outcome::Mismatch(format!("invalid image: {error:?}")); | ||
| 1101 | } | ||
| 1102 | let (ours, theirs) = (normalize(&read(&image, space)), normalize(&read(expected, space))); | ||
| 1103 | if ours != theirs { | ||
| 1104 | return Outcome::Mismatch(first_difference(&theirs, &ours)); | ||
| 1105 | } | ||
| 1106 | if expected == source { | ||
| 1107 | return Outcome::Model; | ||
| 1108 | } | ||
| 1109 | if declared(&image, space) > declared(expected, space) { | ||
| 1110 | return Outcome::Larger; | ||
| 1111 | } | ||
| 1112 | Outcome::Model | ||
| 1113 | } | ||
| 1114 | } | ||
| 1115 | } | ||
| 1116 | |||
| 1117 | fn spans(text: &Paragraph) -> String { | ||
| 1118 | let mut out = format!("{:?}:", text.text()); | ||
| 1119 | for span in text.spans() { | ||
| 1120 | let f = &span.format; | ||
| 1121 | out.push_str(&format!( | ||
| 1122 | " [{} b{:?} h{:?} l{:?}/{:?} c{:?} a{:?} s{:?} lang{:?} f{:?}]", | ||
| 1123 | span.end, f.bold, f.hidden, f.hyperlink, f.hyperlink_label, f.color, f.alignment, f.space_before, f.language, f.font | ||
| 1124 | )); | ||
| 1125 | } | ||
| 1126 | out | ||
| 1127 | } | ||
| 1128 | |||
| 1129 | pub(super) fn first_difference(expected: &Page, actual: &Page) -> String { | ||
| 1130 | let texts = |page: &Page| -> Vec<(ExGuid, Paragraph)> { | ||
| 1131 | model::lists(page) | ||
| 1132 | .into_iter() | ||
| 1133 | .flat_map(|(_, _, list)| list.iter()) | ||
| 1134 | .filter_map(|p| p.text().map(|t| (p.id, t.text.clone()))) | ||
| 1135 | .collect() | ||
| 1136 | }; | ||
| 1137 | for ((a_id, a), (b_id, b)) in texts(expected).iter().zip(texts(actual).iter()) { | ||
| 1138 | if a_id == b_id && a != b { | ||
| 1139 | return format!("\n expected {}\n actual {}", spans(a), spans(b)); | ||
| 1140 | } | ||
| 1141 | } | ||
| 1142 | let (a, b) = (format!("{expected:?}"), format!("{actual:?}")); | ||
| 1143 | let at = a | ||
| 1144 | .bytes() | ||
| 1145 | .zip(b.bytes()) | ||
| 1146 | .position(|(x, y)| x != y) | ||
| 1147 | .unwrap_or(a.len().min(b.len())); | ||
| 1148 | let window = |s: &str| { | ||
| 1149 | let start = s.floor_char_boundary(at.saturating_sub(300)); | ||
| 1150 | let end = s.floor_char_boundary((at + 200).min(s.len())); | ||
| 1151 | s[start..end].to_owned() | ||
| 1152 | }; | ||
| 1153 | format!("\n expected …{}\n actual …{}", window(&a), window(&b)) | ||
| 1154 | } | ||
| 1155 | |||
| 1156 | /// Every family on every corpus page it applies to, several seeds each. | ||
| 1157 | #[test] | ||
| 1158 | fn ops_store_what_the_page_writer_stores() { | ||
| 1159 | let mut tally: BTreeMap<(Family, String), usize> = BTreeMap::new(); | ||
| 1160 | let mut mismatches = Vec::new(); | ||
| 1161 | let seeds = std::env::var("OP_SEEDS").map_or(3, |s| s.parse().unwrap()); | ||
| 1162 | for (name, source) in SOURCES { | ||
| 1163 | for (p, space) in pages(source).into_iter().enumerate().take(4) { | ||
| 1164 | for family in FAMILIES { | ||
| 1165 | for seed in 0..seeds { | ||
| 1166 | let mut rng = Rng(seed * 7919 + p as u64 * 131 + family as u64); | ||
| 1167 | let mut after = read(source, space); | ||
| 1168 | GUIDS.set(Some(1 << 60 | seed << 32 | (family as u64) << 20)); | ||
| 1169 | if !mutate(&mut after, &mut rng, family) { | ||
| 1170 | continue; | ||
| 1171 | } | ||
| 1172 | GUIDS.set(None); | ||
| 1173 | let outcome = differential(source, space, &after, 1 << 40 | seed); | ||
| 1174 | let key = match &outcome { | ||
| 1175 | Outcome::Mismatch(_) => "Mismatch".to_owned(), | ||
| 1176 | other => format!("{other:?}"), | ||
| 1177 | }; | ||
| 1178 | *tally.entry((family, key)).or_default() += 1; | ||
| 1179 | if let Outcome::Mismatch(detail) = outcome { | ||
| 1180 | mismatches.push(format!("{name} page {p} {family:?} seed {seed}: {detail}")); | ||
| 1181 | } | ||
| 1182 | } | ||
| 1183 | } | ||
| 1184 | } | ||
| 1185 | } | ||
| 1186 | let mut report = String::new(); | ||
| 1187 | for ((family, outcome), count) in &tally { | ||
| 1188 | report.push_str(&format!("{family:?} {outcome} {count}\n")); | ||
| 1189 | } | ||
| 1190 | println!("{report}"); | ||
| 1191 | for mismatch in mismatches.iter().take(8) { | ||
| 1192 | println!("{mismatch}\n"); | ||
| 1193 | } | ||
| 1194 | assert!(mismatches.is_empty(), "{} mismatches", mismatches.len()); | ||
| 1195 | assert!(!tally.keys().any(|(_, outcome)| outcome == "Larger")); | ||
| 1196 | } | ||
| 1197 | |||
| 1198 | #[test] | ||
| 1199 | #[ignore] | ||
| 1200 | fn debug_case() { | ||
| 1201 | let (_, source) = SOURCES[5]; | ||
| 1202 | let space = pages(source)[1]; | ||
| 1203 | let family = Family::NewOutline; | ||
| 1204 | let (seed, p) = (1u64, 1u64); | ||
| 1205 | let mut rng = Rng(seed * 7919 + p * 131 + family as u64); | ||
| 1206 | let mut after = read(source, space); | ||
| 1207 | GUIDS.set(Some(1 << 60 | seed << 32 | (family as u64) << 20)); | ||
| 1208 | assert!(mutate(&mut after, &mut rng, family)); | ||
| 1209 | GUIDS.set(None); | ||
| 1210 | let before = read(source, space); | ||
| 1211 | let ops = lower_page(&before, &after).unwrap(); | ||
| 1212 | let mut page = before.clone(); | ||
| 1213 | for op in &ops { | ||
| 1214 | model::apply(&mut page, op).unwrap(); | ||
| 1215 | } | ||
| 1216 | for object in &page.objects { | ||
| 1217 | println!("{:?} {:?} {:?}", object.id(), object.layout(), match object { PageObject::Outline(o) => o.paragraphs.iter().filter_map(|p| p.text().map(|t| t.text.text().to_owned())).collect::<Vec<_>>(), PageObject::Title(t) => t.outlines.iter().flat_map(|o| &o.paragraphs).filter_map(|p| p.text().map(|t| format!("title {:?}", t.text.text()))).collect(), _ => vec![] }); | ||
| 1218 | } | ||
| 1219 | println!("{:?} -> {:?}", before.title, page.title); | ||
| 1220 | } | ||
| 1221 | |||
| 1222 | /// Random ops drawn from a page model: targets the page holds, new identities fresh. | ||
| 1223 | fn random_op(page: &Page, rng: &mut Rng) -> Option<PageOp> { | ||
| 1224 | let texts: Vec<(ExGuid, ExGuid, Paragraph)> = model::lists(page) | ||
| 1225 | .into_iter() | ||
| 1226 | .flat_map(|(_, _, list)| list.iter()) | ||
| 1227 | .filter_map(|p| { | ||
| 1228 | let text = p.text()?; | ||
| 1229 | (text.date_field.is_none() && !crate::page::Math::is_equation(&text.text)) | ||
| 1230 | .then(|| (p.id, text.id, text.text.clone())) | ||
| 1231 | }) | ||
| 1232 | .collect(); | ||
| 1233 | let (paragraph, text, content) = texts.get(rng.pick(texts.len())?)?.clone(); | ||
| 1234 | let length = content.utf16_offset(content.text().len()).unwrap(); | ||
| 1235 | let offsets: Vec<u32> = content | ||
| 1236 | .text() | ||
| 1237 | .char_indices() | ||
| 1238 | .map(|(i, _)| content.utf16_offset(i).unwrap()) | ||
| 1239 | .chain([length]) | ||
| 1240 | .collect(); | ||
| 1241 | let a = offsets[rng.pick(offsets.len()).unwrap()]; | ||
| 1242 | let b = offsets[rng.pick(offsets.len()).unwrap()]; | ||
| 1243 | let range = a.min(b)..a.max(b); | ||
| 1244 | let holder = model::paragraph(page, paragraph).unwrap(); | ||
| 1245 | let container = holder.parent.or_else(|| { | ||
| 1246 | model::lists(page) | ||
| 1247 | .into_iter() | ||
| 1248 | .find(|(_, _, list)| list.iter().any(|p| p.id == paragraph)) | ||
| 1249 | .map(|(_, owner, _)| owner) | ||
| 1250 | })?; | ||
| 1251 | let siblings: Vec<ExGuid> = model::lists(page) | ||
| 1252 | .into_iter() | ||
| 1253 | .flat_map(|(_, _, list)| list.iter()) | ||
| 1254 | .filter(|p| p.parent == holder.parent && p.id != paragraph) | ||
| 1255 | .map(|p| p.id) | ||
| 1256 | .collect(); | ||
| 1257 | if rng.next().is_multiple_of(4) | ||
| 1258 | && let Some(op) = random_object_op(page, rng) | ||
| 1259 | { | ||
| 1260 | return Some(op); | ||
| 1261 | } | ||
| 1262 | Some(match rng.next() % 16 { | ||
| 1263 | 0 | 1 => PageOp::Text { | ||
| 1264 | text, | ||
| 1265 | range, | ||
| 1266 | with: rng.text(), | ||
| 1267 | }, | ||
| 1268 | 2 => PageOp::Format { | ||
| 1269 | text, | ||
| 1270 | range, | ||
| 1271 | set: vec![TextAttribute::Bold(rng.coin())], | ||
| 1272 | clear: if rng.next().is_multiple_of(3) { vec![TextProperty::Italic] } else { Vec::new() }, | ||
| 1273 | }, | ||
| 1274 | 3 => PageOp::Split { | ||
| 1275 | text, | ||
| 1276 | at: a, | ||
| 1277 | paragraph: id(), | ||
| 1278 | right: id(), | ||
| 1279 | lists: holder.lists.iter().map(|_| id()).collect(), | ||
| 1280 | }, | ||
| 1281 | 4 => { | ||
| 1282 | let mut new = text_paragraph(&rng.text(), content.format_at(0).unwrap().clone()); | ||
| 1283 | new.level = holder.level; | ||
| 1284 | new.parent = holder.parent; | ||
| 1285 | PageOp::Insert { | ||
| 1286 | container, | ||
| 1287 | before: (rng.coin()).then_some(paragraph), | ||
| 1288 | paragraphs: vec![new], | ||
| 1289 | } | ||
| 1290 | } | ||
| 1291 | 5 => PageOp::Delete { object: paragraph }, | ||
| 1292 | 6 => PageOp::Move { | ||
| 1293 | object: paragraph, | ||
| 1294 | parent: Some(container), | ||
| 1295 | before: siblings.get(rng.pick(siblings.len().max(1)).unwrap_or(0)).copied(), | ||
| 1296 | }, | ||
| 1297 | 7 => PageOp::Level { | ||
| 1298 | paragraph, | ||
| 1299 | level: (holder.level + 1).clamp(1, 4) - (rng.next() % 2) as u32, | ||
| 1300 | }, | ||
| 1301 | 8 => PageOp::Outline { | ||
| 1302 | object: paragraph, | ||
| 1303 | edit: crate::OutlineEdit::Collapsed(rng.coin()), | ||
| 1304 | }, | ||
| 1305 | 9 => PageOp::Paragraph { | ||
| 1306 | paragraph, | ||
| 1307 | alignment: Some((rng.next() % 3) as u8), | ||
| 1308 | rtl: None, | ||
| 1309 | space_before: Some(4.5), | ||
| 1310 | space_after: None, | ||
| 1311 | line_spacing: None, | ||
| 1312 | language: None, | ||
| 1313 | }, | ||
| 1314 | 10 => { | ||
| 1315 | let right = texts.iter().skip_while(|(p, _, _)| *p != paragraph).nth(1)?; | ||
| 1316 | PageOp::Join { | ||
| 1317 | left: text, | ||
| 1318 | right: right.1, | ||
| 1319 | } | ||
| 1320 | } | ||
| 1321 | 11 => PageOp::List { | ||
| 1322 | paragraph, | ||
| 1323 | lists: vec![( | ||
| 1324 | id(), | ||
| 1325 | Definition { | ||
| 1326 | kind: Kind::List { | ||
| 1327 | font: Some("Calibri".into()), | ||
| 1328 | format: Some("\u{fffd}1.".into()), | ||
| 1329 | restart: None, | ||
| 1330 | bullet: None, | ||
| 1331 | }, | ||
| 1332 | format: Format::default(), | ||
| 1333 | }, | ||
| 1334 | )], | ||
| 1335 | }, | ||
| 1336 | 12 => { | ||
| 1337 | let definition = id(); | ||
| 1338 | PageOp::Tags { | ||
| 1339 | target: if rng.coin() { paragraph } else { text }, | ||
| 1340 | tags: vec![crate::document::Tag { | ||
| 1341 | definition: Some(definition), | ||
| 1342 | action_type: None, | ||
| 1343 | status: 0, | ||
| 1344 | created: Some(1_262_401_445), | ||
| 1345 | completed: None, | ||
| 1346 | start: None, | ||
| 1347 | due: None, | ||
| 1348 | task_id: None, | ||
| 1349 | extra_set: 0, | ||
| 1350 | }], | ||
| 1351 | definitions: vec![( | ||
| 1352 | definition, | ||
| 1353 | Definition { | ||
| 1354 | kind: Kind::TagDefinition { | ||
| 1355 | label: Some("Random".into()), | ||
| 1356 | action_type: Some(0), | ||
| 1357 | shape: Some(3), | ||
| 1358 | color: None, | ||
| 1359 | highlight: None, | ||
| 1360 | }, | ||
| 1361 | format: Format::default(), | ||
| 1362 | }, | ||
| 1363 | )], | ||
| 1364 | } | ||
| 1365 | } | ||
| 1366 | 13 => PageOp::Link { | ||
| 1367 | text, | ||
| 1368 | range, | ||
| 1369 | target: rng.coin().then(|| "https://example.invalid/random".into()), | ||
| 1370 | }, | ||
| 1371 | 14 => PageOp::Add { | ||
| 1372 | object: PageObject::Outline(Outline { | ||
| 1373 | id: id(), | ||
| 1374 | title: false, | ||
| 1375 | min_width: None, | ||
| 1376 | layout: Layout { | ||
| 1377 | x: Some(36.0), | ||
| 1378 | y: Some(300.0 + (rng.next() % 100) as f32), | ||
| 1379 | ..Default::default() | ||
| 1380 | }, | ||
| 1381 | indents: Vec::new(), | ||
| 1382 | paragraphs: vec![text_paragraph(&rng.text(), Format::default())], | ||
| 1383 | unsupported: Vec::new(), | ||
| 1384 | }), | ||
| 1385 | before: None, | ||
| 1386 | }, | ||
| 1387 | _ => PageOp::Add { | ||
| 1388 | object: PageObject::Ink(Ink { | ||
| 1389 | id: id(), | ||
| 1390 | layout: Layout::default(), | ||
| 1391 | strokes: vec![stroke(rng)], | ||
| 1392 | groups: Vec::new(), | ||
| 1393 | }), | ||
| 1394 | before: None, | ||
| 1395 | }, | ||
| 1396 | }) | ||
| 1397 | } | ||
| 1398 | |||
| 1399 | /// Random edits of tables, pictures, ink, outlines and styles the page holds. | ||
| 1400 | fn random_object_op(page: &Page, rng: &mut Rng) -> Option<PageOp> { | ||
| 1401 | let tables: Vec<Table> = model::lists(page) | ||
| 1402 | .into_iter() | ||
| 1403 | .flat_map(|(_, _, list)| list.iter()) | ||
| 1404 | .filter_map(|p| match &p.content { | ||
| 1405 | ParagraphContent::Table(table) => Some(table.clone()), | ||
| 1406 | _ => None, | ||
| 1407 | }) | ||
| 1408 | .collect(); | ||
| 1409 | let outlines: Vec<&Outline> = page | ||
| 1410 | .objects | ||
| 1411 | .iter() | ||
| 1412 | .filter_map(|o| match o { | ||
| 1413 | PageObject::Outline(outline) if !outline.title => Some(outline), | ||
| 1414 | _ => None, | ||
| 1415 | }) | ||
| 1416 | .collect(); | ||
| 1417 | let pictures: Vec<&Image> = page | ||
| 1418 | .objects | ||
| 1419 | .iter() | ||
| 1420 | .filter_map(|o| match o { | ||
| 1421 | PageObject::Image(image) => Some(image), | ||
| 1422 | _ => None, | ||
| 1423 | }) | ||
| 1424 | .collect(); | ||
| 1425 | let inks: Vec<&Ink> = page | ||
| 1426 | .objects | ||
| 1427 | .iter() | ||
| 1428 | .filter_map(|o| match o { | ||
| 1429 | PageObject::Ink(ink) if ink.groups.is_empty() => Some(ink), | ||
| 1430 | _ => None, | ||
| 1431 | }) | ||
| 1432 | .collect(); | ||
| 1433 | match rng.next() % 6 { | ||
| 1434 | 0 | 1 => { | ||
| 1435 | let table = tables.get(rng.pick(tables.len())?)?; | ||
| 1436 | let indents = table.rows[0].cells[0].indents.clone(); | ||
| 1437 | let cell = || TableCell { | ||
| 1438 | paragraphs: vec![text_paragraph("cell", Format::default())], | ||
| 1439 | ..new_cell(&indents) | ||
| 1440 | }; | ||
| 1441 | let edit = match rng.next() % 7 { | ||
| 1442 | 0 => TableEdit::Rows { | ||
| 1443 | before: table.rows.get(rng.pick(table.rows.len() + 1)?).map(|r| r.id), | ||
| 1444 | rows: vec![TableRow { | ||
| 1445 | id: id(), | ||
| 1446 | cells: table.columns.iter().map(|_| cell()).collect(), | ||
| 1447 | }], | ||
| 1448 | }, | ||
| 1449 | 1 => TableEdit::Column { | ||
| 1450 | at: rng.pick(table.columns.len() + 1)? as u32, | ||
| 1451 | width: 60.0, | ||
| 1452 | cells: table.rows.iter().map(|_| cell()).collect(), | ||
| 1453 | }, | ||
| 1454 | 2 => TableEdit::DeleteRow(table.rows[rng.pick(table.rows.len())?].id), | ||
| 1455 | 3 => TableEdit::DeleteColumn(rng.pick(table.columns.len())? as u32), | ||
| 1456 | 4 => TableEdit::Columns( | ||
| 1457 | table | ||
| 1458 | .columns | ||
| 1459 | .iter() | ||
| 1460 | .map(|c| TableColumn { | ||
| 1461 | width: c.width + 9.0, | ||
| 1462 | locked: !c.locked, | ||
| 1463 | }) | ||
| 1464 | .collect(), | ||
| 1465 | ), | ||
| 1466 | 5 => TableEdit::Borders(rng.coin()), | ||
| 1467 | _ => { | ||
| 1468 | let cells: Vec<&TableCell> = table.rows.iter().flat_map(|r| &r.cells).collect(); | ||
| 1469 | let cell = cells[rng.pick(cells.len())?]; | ||
| 1470 | TableEdit::Cell { | ||
| 1471 | cell: cell.id, | ||
| 1472 | shading: Some(0x00ccff), | ||
| 1473 | indents: vec![18.0, 0.0, 27.0, 27.0], | ||
| 1474 | } | ||
| 1475 | } | ||
| 1476 | }; | ||
| 1477 | Some(PageOp::Table { | ||
| 1478 | table: table.id, | ||
| 1479 | edit, | ||
| 1480 | }) | ||
| 1481 | } | ||
| 1482 | 2 => { | ||
| 1483 | let outline = outlines.get(rng.pick(outlines.len())?)?; | ||
| 1484 | Some(PageOp::Outline { | ||
| 1485 | object: outline.id, | ||
| 1486 | edit: if rng.coin() { | ||
| 1487 | crate::OutlineEdit::Position { | ||
| 1488 | x: (rng.next() % 300) as f32, | ||
| 1489 | y: (rng.next() % 300) as f32, | ||
| 1490 | } | ||
| 1491 | } else { | ||
| 1492 | crate::OutlineEdit::Width { | ||
| 1493 | points: 100.0 + (rng.next() % 300) as f32, | ||
| 1494 | user_set: rng.coin(), | ||
| 1495 | } | ||
| 1496 | }, | ||
| 1497 | }) | ||
| 1498 | } | ||
| 1499 | 3 => { | ||
| 1500 | let picture = pictures.get(rng.pick(pictures.len())?)?; | ||
| 1501 | Some(PageOp::Picture { | ||
| 1502 | picture: picture.id, | ||
| 1503 | layout: Layout { | ||
| 1504 | x: Some((rng.next() % 300) as f32), | ||
| 1505 | y: Some((rng.next() % 300) as f32), | ||
| 1506 | max_width: Some(40.0), | ||
| 1507 | max_height: Some(30.0), | ||
| 1508 | width_set_by_user: Some(true), | ||
| 1509 | reserved_width: picture.layout.reserved_width, | ||
| 1510 | }, | ||
| 1511 | alt: Some(rng.text()), | ||
| 1512 | }) | ||
| 1513 | } | ||
| 1514 | 4 => { | ||
| 1515 | let ink = inks.get(rng.pick(inks.len())?)?; | ||
| 1516 | let remove = if ink.strokes.len() > 1 && rng.coin() { | ||
| 1517 | vec![ink.strokes[rng.pick(ink.strokes.len())?].id] | ||
| 1518 | } else { | ||
| 1519 | Vec::new() | ||
| 1520 | }; | ||
| 1521 | Some(PageOp::Strokes { | ||
| 1522 | ink: ink.id, | ||
| 1523 | add: vec![stroke(rng)], | ||
| 1524 | remove, | ||
| 1525 | }) | ||
| 1526 | } | ||
| 1527 | _ => { | ||
| 1528 | let paragraphs: Vec<&PageParagraph> = model::lists(page) | ||
| 1529 | .into_iter() | ||
| 1530 | .flat_map(|(_, _, list)| list.iter()) | ||
| 1531 | .filter(|p| p.text().is_some()) | ||
| 1532 | .collect(); | ||
| 1533 | let paragraph = paragraphs.get(rng.pick(paragraphs.len())?)?; | ||
| 1534 | Some(PageOp::Style { | ||
| 1535 | paragraph: paragraph.id, | ||
| 1536 | style: id(), | ||
| 1537 | definition: Definition { | ||
| 1538 | kind: Kind::Style { | ||
| 1539 | name: Some("Heading 3".into()), | ||
| 1540 | }, | ||
| 1541 | format: Format { | ||
| 1542 | bold: Some(true), | ||
| 1543 | font_size: Some(12.0), | ||
| 1544 | color: Some(0x7d4f1e), | ||
| 1545 | ..Default::default() | ||
| 1546 | }, | ||
| 1547 | }, | ||
| 1548 | }) | ||
| 1549 | } | ||
| 1550 | } | ||
| 1551 | } | ||
| 1552 | |||
| 1553 | /// Random edits through a section, sealed now and then: each applied edit reads back as the | ||
| 1554 | /// model predicts, a refused one changes nothing, and reopening every sealed image gives | ||
| 1555 | /// the same pages. | ||
| 1556 | #[test] | ||
| 1557 | fn random_ops_read_back_as_the_model_predicts() { | ||
| 1558 | let steps = std::env::var("OP_STEPS").map_or(60, |s| s.parse().unwrap()); | ||
| 1559 | let (mut applied, mut refused, mut unseen) = (0, 0, 0); | ||
| 1560 | let mut mismatches: Vec<String> = Vec::new(); | ||
| 1561 | for (name, source) in SOURCES { | ||
| 1562 | let spaces = pages(source); | ||
| 1563 | for seed in 0..2u64 { | ||
| 1564 | let arena = Arena::default(); | ||
| 1565 | let mut section = Section::open(&arena, source.to_vec()).unwrap(); | ||
| 1566 | let mut rng = Rng(seed * 104_729 + name.len() as u64); | ||
| 1567 | for step in 0..steps { | ||
| 1568 | let space = spaces[step % spaces.len().min(3)]; | ||
| 1569 | let before = section.page(space).unwrap(); | ||
| 1570 | let ops: Vec<Op> = (0..1 + rng.next() % 2) | ||
| 1571 | .filter_map(|_| random_op(&before, &mut rng)) | ||
| 1572 | .map(|op| Op::Page { space, op }) | ||
| 1573 | .collect(); | ||
| 1574 | if ops.is_empty() { | ||
| 1575 | continue; | ||
| 1576 | } | ||
| 1577 | let edit = Edit { at: AT + step as u64, ops }; | ||
| 1578 | // Text whose emptied final run keeps an insertion style the model cannot show. | ||
| 1579 | let hidden: Vec<ExGuid> = section | ||
| 1580 | .active(space) | ||
| 1581 | .unwrap() | ||
| 1582 | .view | ||
| 1583 | .nodes | ||
| 1584 | .iter() | ||
| 1585 | .filter(|(_, node)| { | ||
| 1586 | matches!(&node.kind, Kind::RichText { text, runs, .. } | ||
| 1587 | if !text.is_empty() && runs.len() > 1 && runs.last().is_some_and(|r| r.start == r.end)) | ||
| 1588 | }) | ||
| 1589 | .map(|(id, _)| *id) | ||
| 1590 | .collect(); | ||
| 1591 | match section.apply("Author", &edit) { | ||
| 1592 | Ok(()) => { | ||
| 1593 | applied += 1; | ||
| 1594 | let mut predicted = before.clone(); | ||
| 1595 | for op in &edit.ops { | ||
| 1596 | let Op::Page { op, .. } = op else { unreachable!() }; | ||
| 1597 | model::apply(&mut predicted, op).unwrap(); | ||
| 1598 | } | ||
| 1599 | let stored = section.page(space).unwrap(); | ||
| 1600 | let touches_hidden = edit.ops.iter().any(|op| { | ||
| 1601 | matches!(op, Op::Page { op: PageOp::Text { text, .. } | PageOp::Link { text, .. } | PageOp::Split { text, .. }, .. } if hidden.contains(text)) | ||
| 1602 | }); | ||
| 1603 | // A restyled run may state what its old style did, which the model | ||
| 1604 | // cannot tell from inheriting it. | ||
| 1605 | let restyles = edit.ops.iter().any(|op| { | ||
| 1606 | matches!(op, Op::Page { op: PageOp::Style { paragraph, .. }, .. } | ||
| 1607 | if model::paragraph(&before, *paragraph).is_some_and(|p| p.style.is_some())) | ||
| 1608 | }); | ||
| 1609 | if (touches_hidden || restyles) && normalize(&predicted) != normalize(&stored) { | ||
| 1610 | unseen += 1; | ||
| 1611 | continue; | ||
| 1612 | } | ||
| 1613 | if normalize(&predicted) != normalize(&stored) { | ||
| 1614 | let kinds: Vec<String> = edit | ||
| 1615 | .ops | ||
| 1616 | .iter() | ||
| 1617 | .map(|op| match op { | ||
| 1618 | Op::Page { op, .. } => format!("{op:?}").split([' ', '{']).next().unwrap().to_owned(), | ||
| 1619 | Op::Section(_) => "Section".into(), | ||
| 1620 | }) | ||
| 1621 | .collect(); | ||
| 1622 | if std::env::var("OP_DEBUG").is_ok() { | ||
| 1623 | let tree = |page: &Page| -> String { | ||
| 1624 | model::lists(page) | ||
| 1625 | .into_iter() | ||
| 1626 | .map(|(_, owner, list)| { | ||
| 1627 | format!( | ||
| 1628 | "{}: {}", | ||
| 1629 | owner.n, | ||
| 1630 | list.iter() | ||
| 1631 | .map(|p| format!("{}<{:?}>@{}", p.id.n, p.parent.map(|p| p.n), p.level)) | ||
| 1632 | .collect::<Vec<_>>() | ||
| 1633 | .join(" ") | ||
| 1634 | ) | ||
| 1635 | }) | ||
| 1636 | .collect::<Vec<_>>() | ||
| 1637 | .join("\n") | ||
| 1638 | }; | ||
| 1639 | println!("ops {:?}\nbefore\n{}\nstored\n{}\npredicted\n{}", edit.ops, tree(&before), tree(&stored), tree(&predicted)); | ||
| 1640 | } | ||
| 1641 | if mismatches.len() < 6 { | ||
| 1642 | println!( | ||
| 1643 | "{name} seed {seed} step {step} {kinds:?}: {}", | ||
| 1644 | first_difference(&normalize(&stored), &normalize(&predicted)) | ||
| 1645 | ); | ||
| 1646 | } | ||
| 1647 | mismatches.push(kinds.join("+")); | ||
| 1648 | } | ||
| 1649 | } | ||
| 1650 | Err(OpError::Failed(error)) => panic!("{name} step {step}: {error:?}"), | ||
| 1651 | Err(_) => { | ||
| 1652 | refused += 1; | ||
| 1653 | assert!(section.page(space).unwrap() == before, "{name} step {step}: a refused edit changed the page"); | ||
| 1654 | } | ||
| 1655 | } | ||
| 1656 | if step % 7 == 6 || step + 1 == steps { | ||
| 1657 | section.seal().unwrap(); | ||
| 1658 | let image = section.image(); | ||
| 1659 | let reopened = Section::open(&arena, image).unwrap(); | ||
| 1660 | for space in &spaces { | ||
| 1661 | assert!(reopened.page(*space).unwrap() == section.page(*space).unwrap(), "{name} step {step}: reopening changed a page"); | ||
| 1662 | } | ||
| 1663 | } | ||
| 1664 | } | ||
| 1665 | } | ||
| 1666 | } | ||
| 1667 | println!("{applied} edits applied, {refused} refused, {unseen} restyled or typed into a hidden insertion style"); | ||
| 1668 | let mut kinds = BTreeMap::new(); | ||
| 1669 | for kind in &mismatches { | ||
| 1670 | *kinds.entry(kind.clone()).or_insert(0) += 1; | ||
| 1671 | } | ||
| 1672 | println!("model mispredictions: {kinds:?}"); | ||
| 1673 | assert!(mismatches.is_empty()); | ||
| 1674 | assert!(applied > refused); | ||
| 1675 | } | ||
| 1676 | |||
| 1677 | /// Page creation, moves, indentation and removal through a section store what the image | ||
| 1678 | /// writers store, and the section lists the pages the document does. | ||
| 1679 | #[test] | ||
| 1680 | fn section_ops_store_what_the_image_writers_store() { | ||
| 1681 | let sources: Vec<Vec<u8>> = vec![ | ||
| 1682 | crate::create_section("pages.one", "First", "Author").unwrap(), | ||
| 1683 | include_bytes!("../../../../corpus/page-lifecycle/04-nested/notebook/Lifecycle.one").to_vec(), | ||
| 1684 | SOURCES[3].1.to_vec(), | ||
| 1685 | ]; | ||
| 1686 | let (mut bytes, mut total) = (0, 0); | ||
| 1687 | for (n, source) in sources.iter().enumerate() { | ||
| 1688 | let arena = Arena::default(); | ||
| 1689 | let mut section = Section::open(&arena, source.to_vec()).unwrap(); | ||
| 1690 | let mut image = source.clone(); | ||
| 1691 | let mut rng = Rng(n as u64 + 17); | ||
| 1692 | for step in 0..12u64 { | ||
| 1693 | let spaces = pages(&image); | ||
| 1694 | let op = match step % 4 { | ||
| 1695 | 0 => SectionOp::Create( | ||
| 1696 | seeded(1 << 50 | step << 20, || { | ||
| 1697 | crate::PageCreation::new( | ||
| 1698 | spaces.get(rng.pick(spaces.len()).unwrap_or(0)).copied().filter(|_| rng.coin()), | ||
| 1699 | Some("Created"), | ||
| 1700 | "Author", | ||
| 1701 | ) | ||
| 1702 | }) | ||
| 1703 | .unwrap(), | ||
| 1704 | ), | ||
| 1705 | 1 => { | ||
| 1706 | let Some(space) = spaces.get(rng.pick(spaces.len()).unwrap_or(0)).copied() else { continue }; | ||
| 1707 | let level = if spaces[0] == space { 1 } else { 1 + (rng.next() % 2) as u32 }; | ||
| 1708 | SectionOp::Pages(vec![seeded(1 << 51 | step << 20, || crate::PageEdit::set_level(space, level)).unwrap()]) | ||
| 1709 | } | ||
| 1710 | 2 => { | ||
| 1711 | let Some(space) = spaces.get(rng.pick(spaces.len()).unwrap_or(0)).copied() else { continue }; | ||
| 1712 | SectionOp::Pages(vec![seeded(1 << 52 | step << 20, || crate::PageEdit::move_to(space, None, 1)).unwrap()]) | ||
| 1713 | } | ||
| 1714 | _ => { | ||
| 1715 | if spaces.len() < 2 { | ||
| 1716 | continue; | ||
| 1717 | } | ||
| 1718 | SectionOp::Delete(vec![spaces[rng.pick(spaces.len()).unwrap()]]) | ||
| 1719 | } | ||
| 1720 | }; | ||
| 1721 | let expected = seeded(1 << 40 | step << 16, || { | ||
| 1722 | crate::create::at(AT, || match &op { | ||
| 1723 | SectionOp::Create(creation) => crate::PreparedEdit::create_page(&image, creation), | ||
| 1724 | SectionOp::Pages(edits) => crate::PreparedEdit::pages(&image, edits), | ||
| 1725 | SectionOp::Delete(spaces) => crate::PreparedEdit::delete_pages_permanently(&image, spaces), | ||
| 1726 | SectionOp::Import { .. } => unreachable!(), | ||
| 1727 | }) | ||
| 1728 | .map(|edit| edit.as_bytes().to_vec()) | ||
| 1729 | }); | ||
| 1730 | let applied = seeded(1 << 40 | step << 16, || { | ||
| 1731 | section | ||
| 1732 | .apply("Author", &Edit { at: AT, ops: vec![Op::Section(op.clone())] }) | ||
| 1733 | .map(|()| section.seal().unwrap()) | ||
| 1734 | }); | ||
| 1735 | match (expected, applied) { | ||
| 1736 | (Err(_), Err(_)) => continue, | ||
| 1737 | (Ok(expected), Ok(_)) => { | ||
| 1738 | total += 1; | ||
| 1739 | let written = section.image(); | ||
| 1740 | if written == expected { | ||
| 1741 | bytes += 1; | ||
| 1742 | } else { | ||
| 1743 | let store = Store::parse(&written).unwrap(); | ||
| 1744 | RevisionIndex::parse(&store).unwrap().validate_current().unwrap(); | ||
| 1745 | let spaces = pages(&expected); | ||
| 1746 | assert_eq!(pages(&written), spaces, "{n} step {step} {op:?}"); | ||
| 1747 | for space in spaces { | ||
| 1748 | assert_eq!(read(&written, space), read(&expected, space), "{n} step {step}"); | ||
| 1749 | } | ||
| 1750 | } | ||
| 1751 | image = expected; | ||
| 1752 | let store = Store::parse(&image).unwrap(); | ||
| 1753 | let index = RevisionIndex::parse(&store).unwrap(); | ||
| 1754 | let document = Document::parse(&index).unwrap(); | ||
| 1755 | let listed: Vec<(ExGuid, String, u32)> = document | ||
| 1756 | .pages() | ||
| 1757 | .unwrap() | ||
| 1758 | .into_iter() | ||
| 1759 | .map(|(space, page)| { | ||
| 1760 | let (title, level) = Page::heading(document.active(space).unwrap(), page); | ||
| 1761 | (space, title, level) | ||
| 1762 | }) | ||
| 1763 | .collect(); | ||
| 1764 | assert_eq!(section.pages().unwrap(), listed, "{n} step {step}"); | ||
| 1765 | } | ||
| 1766 | (expected, applied) => panic!("{n} step {step} {op:?}: {:?} vs {:?}", expected.err(), applied.err()), | ||
| 1767 | } | ||
| 1768 | } | ||
| 1769 | } | ||
| 1770 | println!("{bytes} of {total} section edits byte-identical"); | ||
| 1771 | assert!(total > 12); | ||
| 1772 | } | ||
| 1773 | |||
| 1774 | /// Importing a page stores what creating it and writing its copy through the page writer | ||
| 1775 | /// does. | ||
| 1776 | #[test] | ||
| 1777 | fn an_imported_page_reads_as_the_page_writer_leaves_it() { | ||
| 1778 | let target = crate::create_section("import.one", "First", "Author").unwrap(); | ||
| 1779 | let mut imported = 0; | ||
| 1780 | for (name, source) in SOURCES { | ||
| 1781 | for space in pages(source).into_iter().take(2) { | ||
| 1782 | let Ok(copy) = read(source, space).copy() else { | ||
| 1783 | continue; | ||
| 1784 | }; | ||
| 1785 | let creation = crate::PageCreation::new(None, Some(&copy.title), "Author").unwrap(); | ||
| 1786 | let created = crate::PreparedEdit::create_page(&target, &creation).unwrap(); | ||
| 1787 | let created = created.as_bytes(); | ||
| 1788 | let mut after = read(created, creation.space()); | ||
| 1789 | after.objects.retain(|object| matches!(object, PageObject::Title(_))); | ||
| 1790 | after.objects.extend( | ||
| 1791 | copy.objects | ||
| 1792 | .iter() | ||
| 1793 | .filter(|object| !matches!(object, PageObject::Title(_))) | ||
| 1794 | .cloned(), | ||
| 1795 | ); | ||
| 1796 | after.definitions = copy.definitions.clone(); | ||
| 1797 | let expected = crate::create::at(AT, || { | ||
| 1798 | crate::PreparedEdit::page(created, creation.space(), &after, "Author") | ||
| 1799 | }); | ||
| 1800 | let arena = Arena::default(); | ||
| 1801 | let mut section = Section::open(&arena, target.clone()).unwrap(); | ||
| 1802 | let applied = section.apply( | ||
| 1803 | "Author", | ||
| 1804 | &Edit { | ||
| 1805 | at: AT, | ||
| 1806 | ops: vec![Op::Section(SectionOp::Import { | ||
| 1807 | creation: creation.clone(), | ||
| 1808 | page: copy.clone(), | ||
| 1809 | })], | ||
| 1810 | }, | ||
| 1811 | ); | ||
| 1812 | match (expected, applied) { | ||
| 1813 | (Err(_), Err(_)) => continue, | ||
| 1814 | (Ok(expected), Ok(())) => { | ||
| 1815 | section.seal().unwrap(); | ||
| 1816 | let image = section.image(); | ||
| 1817 | let store = Store::parse(&image).unwrap(); | ||
| 1818 | RevisionIndex::parse(&store).unwrap().validate_current().unwrap(); | ||
| 1819 | let (ours, theirs) = ( | ||
| 1820 | normalize(&read(&image, creation.space())), | ||
| 1821 | normalize(&read(expected.as_bytes(), creation.space())), | ||
| 1822 | ); | ||
| 1823 | assert!(ours == theirs, "{name}: {}", first_difference(&theirs, &ours)); | ||
| 1824 | imported += 1; | ||
| 1825 | } | ||
| 1826 | (expected, applied) => panic!("{name}: {:?} vs {:?}", expected.err(), applied.err()), | ||
| 1827 | } | ||
| 1828 | } | ||
| 1829 | } | ||
| 1830 | assert!(imported > 10, "{imported} pages imported"); | ||
| 1831 | } | ||
| 1832 | |||
| 1833 | /// The ops for the changed paragraphs of one outline, lowered from them alone. | ||
| 1834 | fn lower_range(before: &Page, after: &Page) -> Option<Result<Vec<PageOp>, crate::Error>> { | ||
| 1835 | if before.objects.len() != after.objects.len() { | ||
| 1836 | return None; | ||
| 1837 | } | ||
| 1838 | let mut changed = None; | ||
| 1839 | for (a, b) in before.objects.iter().zip(&after.objects) { | ||
| 1840 | match (a, b) { | ||
| 1841 | (PageObject::Outline(a), PageObject::Outline(b)) if a.id == b.id => { | ||
| 1842 | if a.paragraphs != b.paragraphs { | ||
| 1843 | if changed.is_some() || (Outline { paragraphs: Vec::new(), ..a.clone() }) != (Outline { paragraphs: Vec::new(), ..b.clone() }) { | ||
| 1844 | return None; | ||
| 1845 | } | ||
| 1846 | changed = Some((a, b)); | ||
| 1847 | } | ||
| 1848 | } | ||
| 1849 | (a, b) if a == b => {} | ||
| 1850 | _ => return None, | ||
| 1851 | } | ||
| 1852 | } | ||
| 1853 | let (a, b) = changed?; | ||
| 1854 | let (a, b) = (&a.paragraphs, &b.paragraphs); | ||
| 1855 | let prefix = a.iter().zip(b).take_while(|(x, y)| x == y).count(); | ||
| 1856 | let suffix = a[prefix..] | ||
| 1857 | .iter() | ||
| 1858 | .rev() | ||
| 1859 | .zip(b[prefix..].iter().rev()) | ||
| 1860 | .take_while(|(x, y)| x == y) | ||
| 1861 | .count(); | ||
| 1862 | let (mut end_a, mut end_b) = (a.len() - suffix, b.len() - suffix); | ||
| 1863 | // A range holds the children of what it changes. | ||
| 1864 | while end_a < a.len() { | ||
| 1865 | let ids: Vec<ExGuid> = a[prefix..end_a].iter().chain(&b[prefix..end_b]).map(|p| p.id).collect(); | ||
| 1866 | if a[end_a].parent.is_some_and(|parent| ids.contains(&parent)) { | ||
| 1867 | end_a += 1; | ||
| 1868 | end_b += 1; | ||
| 1869 | } else { | ||
| 1870 | break; | ||
| 1871 | } | ||
| 1872 | } | ||
| 1873 | let outline = match before.objects.iter().find(|o| matches!(o, PageObject::Outline(o) if o.paragraphs == *a)) { | ||
| 1874 | Some(PageObject::Outline(outline)) => outline.id, | ||
| 1875 | _ => return None, | ||
| 1876 | }; | ||
| 1877 | let mut definitions = before.definitions.clone(); | ||
| 1878 | definitions.extend(after.definitions.clone()); | ||
| 1879 | Some(lower(outline, &a[prefix..end_a], &b[prefix..end_b], b.get(end_b), &definitions)) | ||
| 1880 | } | ||
| 1881 | |||
| 1882 | /// Lowering the changed range of an outline stores what lowering the whole page does. | ||
| 1883 | #[test] | ||
| 1884 | fn a_range_lowers_as_its_page_does() { | ||
| 1885 | let families = [ | ||
| 1886 | Family::Text, Family::Format, Family::Insert, Family::Paste, Family::Delete, Family::Move, | ||
| 1887 | Family::Collapse, Family::List, Family::Tag, Family::Split, Family::Join, Family::Indent, | ||
| 1888 | Family::Outdent, Family::Alignment, Family::Style, Family::Link, Family::NewTable, | ||
| 1889 | Family::ParagraphPicture, Family::Attachment, Family::Equation, | ||
| 1890 | ]; | ||
| 1891 | let (mut same, mut total, mut smaller) = (0, 0, 0); | ||
| 1892 | let seeds = std::env::var("OP_SEEDS").map_or(3, |s| s.parse().unwrap()); | ||
| 1893 | for (name, source) in SOURCES { | ||
| 1894 | for (p, space) in pages(source).into_iter().enumerate().take(4) { | ||
| 1895 | for family in families { | ||
| 1896 | for seed in 0..seeds { | ||
| 1897 | let mut rng = Rng(seed * 7919 + p as u64 * 131 + family as u64); | ||
| 1898 | let before = read(source, space); | ||
| 1899 | let mut after = before.clone(); | ||
| 1900 | if !mutate(&mut after, &mut rng, family) { | ||
| 1901 | continue; | ||
| 1902 | } | ||
| 1903 | let Some(ranged) = lower_range(&before, &after) else { | ||
| 1904 | continue; | ||
| 1905 | }; | ||
| 1906 | let whole = lower_page(&before, &after); | ||
| 1907 | let (Ok(ranged), Ok(whole)) = (ranged, whole) else { | ||
| 1908 | continue; | ||
| 1909 | }; | ||
| 1910 | total += 1; | ||
| 1911 | let write = |ops: &[PageOp]| { | ||
| 1912 | let arena = Arena::default(); | ||
| 1913 | let mut section = Section::open(&arena, source.to_vec()).unwrap(); | ||
| 1914 | let edit = Edit { | ||
| 1915 | at: AT, | ||
| 1916 | ops: ops.iter().map(|op| Op::Page { space, op: op.clone() }).collect(), | ||
| 1917 | }; | ||
| 1918 | section.apply("Author", &edit).map(|()| normalize(&section.page(space).unwrap())) | ||
| 1919 | }; | ||
| 1920 | let (ours, theirs) = (write(&ranged), write(&whole)); | ||
| 1921 | match (ours, theirs) { | ||
| 1922 | (Ok(ours), Ok(theirs)) => { | ||
| 1923 | assert!(ours == theirs, "{name} page {p} {family:?} seed {seed}: {}", first_difference(&theirs, &ours)); | ||
| 1924 | same += 1; | ||
| 1925 | } | ||
| 1926 | (Err(_), Err(_)) => same += 1, | ||
| 1927 | (ours, theirs) => panic!("{name} page {p} {family:?} seed {seed}: {ours:?} vs {theirs:?}"), | ||
| 1928 | } | ||
| 1929 | let length = |ops: &[PageOp]| format!("{ops:?}").len(); | ||
| 1930 | smaller += usize::from(length(&ranged) <= length(&whole)); | ||
| 1931 | } | ||
| 1932 | } | ||
| 1933 | } | ||
| 1934 | } | ||
| 1935 | println!("{same} of {total} ranges store what their pages do; {smaller} lowered no larger"); | ||
| 1936 | assert_eq!(same, total); | ||
| 1937 | assert!(total > 100); | ||
| 1938 | } | ||
| 1939 | |||
| 1940 | /// One keystroke as ops on the 3000-paragraph probe page: | ||
| 1941 | /// `SECTION_PROBE=path cargo test --release -p onestore --lib keystroke_ops -- --ignored --nocapture`. | ||
| 1942 | #[test] | ||
| 1943 | #[ignore] | ||
| 1944 | fn keystroke_ops() { | ||
| 1945 | use std::time::{Duration, Instant}; | ||
| 1946 | fn median(mut samples: Vec<Duration>) -> Duration { | ||
| 1947 | samples.sort(); | ||
| 1948 | samples[samples.len() / 2] | ||
| 1949 | } | ||
| 1950 | let path = std::env::var("SECTION_PROBE").unwrap_or("/tmp/probe3000.one".into()); | ||
| 1951 | let image = std::fs::read(&path).unwrap(); | ||
| 1952 | let space = pages(&image) | ||
| 1953 | .into_iter() | ||
| 1954 | .max_by_key(|space| read(&image, *space).objects.len() + format!("{:?}", read(&image, *space)).len()) | ||
| 1955 | .unwrap(); | ||
| 1956 | let page = read(&image, space); | ||
| 1957 | let outline = page | ||
| 1958 | .objects | ||
| 1959 | .iter() | ||
| 1960 | .filter_map(|o| match o { | ||
| 1961 | PageObject::Outline(outline) => Some(outline), | ||
| 1962 | _ => None, | ||
| 1963 | }) | ||
| 1964 | .max_by_key(|o| o.paragraphs.len()) | ||
| 1965 | .unwrap(); | ||
| 1966 | let middle = outline.paragraphs.len() / 2; | ||
| 1967 | let paragraph = &outline.paragraphs[middle]; | ||
| 1968 | let text = paragraph.text().unwrap().id; | ||
| 1969 | println!("{path}: {} bytes, {} paragraphs in the outline", image.len(), outline.paragraphs.len()); | ||
| 1970 | |||
| 1971 | // Emitting: a keystroke is one op; a generic range replacement lowers its paragraphs. | ||
| 1972 | let mut edited = paragraph.clone(); | ||
| 1973 | let format = edited.text().unwrap().text.format_at(0).unwrap().clone(); | ||
| 1974 | edited | ||
| 1975 | .text_mut() | ||
| 1976 | .unwrap() | ||
| 1977 | .text | ||
| 1978 | .apply(crate::page::text::Edit { | ||
| 1979 | range: 0..0, | ||
| 1980 | replacement: Paragraph::new("x".into(), format), | ||
| 1981 | }) | ||
| 1982 | .unwrap(); | ||
| 1983 | let mut samples = Vec::new(); | ||
| 1984 | for _ in 0..200 { | ||
| 1985 | let start = Instant::now(); | ||
| 1986 | let ops = lower( | ||
| 1987 | outline.id, | ||
| 1988 | std::slice::from_ref(paragraph), | ||
| 1989 | std::slice::from_ref(&edited), | ||
| 1990 | outline.paragraphs.get(middle + 1), | ||
| 1991 | &page.definitions, | ||
| 1992 | ) | ||
| 1993 | .unwrap(); | ||
| 1994 | samples.push(start.elapsed()); | ||
| 1995 | assert_eq!(ops.len(), 1); | ||
| 1996 | } | ||
| 1997 | println!("lower one paragraph: {:?}", median(samples)); | ||
| 1998 | |||
| 1999 | let arena = Arena::default(); | ||
| 2000 | let start = Instant::now(); | ||
| 2001 | let mut section = Section::open(&arena, image.clone()).unwrap(); | ||
| 2002 | println!("Section::open: {:?}", start.elapsed()); | ||
| 2003 | let start = Instant::now(); | ||
| 2004 | section.active(space).unwrap(); | ||
| 2005 | println!("first edit opens the page: {:?}", start.elapsed()); | ||
| 2006 | // Five keystrokes a second, each sealed: the outline's modification time ticks once a | ||
| 2007 | // second, which re-declares its child list. | ||
| 2008 | let (mut applies, mut seals, mut sizes) = (Vec::new(), Vec::new(), Vec::new()); | ||
| 2009 | for keystroke in 0..300u32 { | ||
| 2010 | let edit = Edit { | ||
| 2011 | at: AT + u64::from(keystroke) * 2_000_000, | ||
| 2012 | ops: vec![Op::Page { | ||
| 2013 | space, | ||
| 2014 | op: PageOp::Text { | ||
| 2015 | text, | ||
| 2016 | range: keystroke..keystroke, | ||
| 2017 | with: "x".into(), | ||
| 2018 | }, | ||
| 2019 | }], | ||
| 2020 | }; | ||
| 2021 | let start = Instant::now(); | ||
| 2022 | section.apply("Probe", &edit).unwrap(); | ||
| 2023 | applies.push(start.elapsed()); | ||
| 2024 | let start = Instant::now(); | ||
| 2025 | let transaction = section.seal().unwrap().unwrap(); | ||
| 2026 | seals.push(start.elapsed()); | ||
| 2027 | sizes.push(( | ||
| 2028 | transaction.append.len(), | ||
| 2029 | transaction.patches.iter().map(|(_, bytes)| bytes.len()).sum::<usize>(), | ||
| 2030 | )); | ||
| 2031 | } | ||
| 2032 | sizes.sort(); | ||
| 2033 | println!( | ||
| 2034 | "Section::apply(Text) {:?}, seal {:?}; appended {} bytes (max {}), patched {} bytes (median of 300)", | ||
| 2035 | median(applies), | ||
| 2036 | median(seals), | ||
| 2037 | sizes[sizes.len() / 2].0, | ||
| 2038 | sizes.last().unwrap().0, | ||
| 2039 | sizes[sizes.len() / 2].1, | ||
| 2040 | ); | ||
| 2041 | let start = Instant::now(); | ||
| 2042 | let reopened = Section::open(&arena, section.image()).unwrap(); | ||
| 2043 | println!("reopen after 300 seals: {:?}", start.elapsed()); | ||
| 2044 | assert_eq!(reopened.page(space).unwrap(), section.page(space).unwrap()); | ||
| 2045 | } | ||
| 2046 | |||
| 2047 | /// Writes a notebook for the native cold-open gate of the op path to `OP_GATE_EXPORT`: | ||
| 2048 | /// corpus pages imported as ops, then edited by lowered page edits of every family, new | ||
| 2049 | /// pages, an outline move on a page without a margin origin, page moves and a removal. | ||
| 2050 | #[test] | ||
| 2051 | #[ignore] | ||
| 2052 | fn op_gate_candidate() { | ||
| 2053 | let directory = std::path::PathBuf::from(std::env::var("OP_GATE_EXPORT").unwrap()); | ||
| 2054 | std::fs::create_dir_all(&directory).unwrap(); | ||
| 2055 | let source = crate::create_section("ops.one", "Ops gate", "Author").unwrap(); | ||
| 2056 | let arena = Arena::default(); | ||
| 2057 | let mut section = Section::open(&arena, source).unwrap(); | ||
| 2058 | let at = std::cell::Cell::new(AT); | ||
| 2059 | let next = || { | ||
| 2060 | at.set(at.get() + 20_000_000); | ||
| 2061 | at.get() | ||
| 2062 | }; | ||
| 2063 | let apply = |section: &mut Section<'_>, op: Op| { | ||
| 2064 | let result = section.apply("Gate author", &Edit { at: next(), ops: vec![op] }); | ||
| 2065 | section.seal().unwrap(); | ||
| 2066 | result | ||
| 2067 | }; | ||
| 2068 | let mut imported = Vec::new(); | ||
| 2069 | for (name, bytes) in SOURCES.iter().filter(|(name, _)| *name != "enter-probe") { | ||
| 2070 | let Some(space) = pages(bytes).first().copied() else { continue }; | ||
| 2071 | let page = read(bytes, space); | ||
| 2072 | if page.objects.iter().any(|o| matches!(o, PageObject::Unsupported(_))) { | ||
| 2073 | continue; | ||
| 2074 | } | ||
| 2075 | let Ok(copy) = page.copy() else { continue }; | ||
| 2076 | let creation = crate::PageCreation::new(None, Some(&format!("Imported {name}")), "Gate author").unwrap(); | ||
| 2077 | if apply(&mut section, Op::Section(SectionOp::Import { creation: creation.clone(), page: copy })).is_ok() { | ||
| 2078 | imported.push(creation.space()); | ||
| 2079 | } | ||
| 2080 | } | ||
| 2081 | let mut edits = 0; | ||
| 2082 | for (n, space) in imported.iter().enumerate() { | ||
| 2083 | for (k, family) in FAMILIES.iter().enumerate() { | ||
| 2084 | if (k + n) % 4 != 0 { | ||
| 2085 | continue; | ||
| 2086 | } | ||
| 2087 | let before = section.page(*space).unwrap(); | ||
| 2088 | let mut after = before.clone(); | ||
| 2089 | let mut rng = Rng((n * 100 + k) as u64); | ||
| 2090 | if !mutate(&mut after, &mut rng, *family) { | ||
| 2091 | continue; | ||
| 2092 | } | ||
| 2093 | // Edits inside a field code leave a field OneNote and this reader resolve apart, | ||
| 2094 | // whichever writer stores them. | ||
| 2095 | let fields = |page: &Page| -> Vec<(ExGuid, String)> { | ||
| 2096 | model::lists(page) | ||
| 2097 | .into_iter() | ||
| 2098 | .flat_map(|(_, _, list)| list.iter()) | ||
| 2099 | .filter_map(|p| p.text()) | ||
| 2100 | .filter(|t| t.text.text().contains('\u{fddf}')) | ||
| 2101 | .map(|t| (t.id, t.text.text().to_owned())) | ||
| 2102 | .collect() | ||
| 2103 | }; | ||
| 2104 | let edited = fields(&before); | ||
| 2105 | if fields(&after).iter().any(|(id, text)| edited.iter().any(|(e, old)| e == id && old != text)) { | ||
| 2106 | continue; | ||
| 2107 | } | ||
| 2108 | if section.apply_page("Gate author", next(), *space, &after).is_ok() { | ||
| 2109 | section.seal().unwrap(); | ||
| 2110 | edits += 1; | ||
| 2111 | } | ||
| 2112 | } | ||
| 2113 | } | ||
| 2114 | // A created page: margins, an outline, and its first move adding the margin origin. | ||
| 2115 | let creation = crate::PageCreation::new(None, Some("Created by ops"), "Gate author").unwrap(); | ||
| 2116 | apply(&mut section, Op::Section(SectionOp::Create(creation.clone()))).unwrap(); | ||
| 2117 | let outline = Outline { | ||
| 2118 | id: id(), | ||
| 2119 | title: false, | ||
| 2120 | min_width: None, | ||
| 2121 | layout: Layout { x: Some(36.0), y: Some(86.4), ..Default::default() }, | ||
| 2122 | indents: Vec::new(), | ||
| 2123 | paragraphs: vec![text_paragraph("Typed through ops", Format::default())], | ||
| 2124 | unsupported: Vec::new(), | ||
| 2125 | }; | ||
| 2126 | let outline_id = outline.id; | ||
| 2127 | let text = outline.paragraphs[0].text().unwrap().id; | ||
| 2128 | let space = creation.space(); | ||
| 2129 | let page = |op| Op::Page { space, op }; | ||
| 2130 | apply(&mut section, page(PageOp::Add { object: PageObject::Outline(outline), before: None })).unwrap(); | ||
| 2131 | apply(&mut section, page(PageOp::Outline { object: outline_id, edit: crate::OutlineEdit::Position { x: 72.0, y: 108.0 } })).unwrap(); | ||
| 2132 | apply(&mut section, page(PageOp::Text { text, range: 5..5, with: " and sealed".into() })).unwrap(); | ||
| 2133 | apply(&mut section, page(PageOp::Link { text, range: 0..5, target: Some("https://example.invalid/ops".into()) })).unwrap(); | ||
| 2134 | let spaces: Vec<ExGuid> = section.pages().unwrap().into_iter().map(|(s, _, _)| s).collect(); | ||
| 2135 | apply(&mut section, Op::Section(SectionOp::Pages(vec![crate::PageEdit::move_to(space, Some(spaces[1]), 1).unwrap()]))).unwrap(); | ||
| 2136 | apply(&mut section, Op::Section(SectionOp::Pages(vec![crate::PageEdit::set_level(spaces[2], 2).unwrap()]))).unwrap(); | ||
| 2137 | apply(&mut section, Op::Section(SectionOp::Delete(vec![*spaces.last().unwrap()]))).unwrap(); | ||
| 2138 | let image = section.image(); | ||
| 2139 | let store = Store::parse(&image).unwrap(); | ||
| 2140 | RevisionIndex::parse(&store).unwrap().validate_current().unwrap(); | ||
| 2141 | let count = section.pages().unwrap().len(); | ||
| 2142 | std::fs::write(directory.join("ops.one"), &image).unwrap(); | ||
| 2143 | std::fs::write( | ||
| 2144 | directory.join("Open Notebook.onetoc2"), | ||
| 2145 | crate::create_table_of_contents("Open Notebook.onetoc2", &[("ops.one", store.header.file_id)]).unwrap(), | ||
| 2146 | ) | ||
| 2147 | .unwrap(); | ||
| 2148 | std::fs::write(directory.join("expected-count.txt"), count.to_string()).unwrap(); | ||
| 2149 | println!("{} imported pages, {edits} lowered edits, {count} pages", imported.len()); | ||
| 2150 | } | ||
crates/onestore/src/outline.rs+11| ... | @@ -126,6 +126,17 @@ impl OutlineEdit { | ... | @@ -126,6 +126,17 @@ impl OutlineEdit { |
| 126 | } | 126 | } |
| 127 | } | 127 | } |
| 128 | if matches!(self, Self::Position { .. }) { | 128 | if matches!(self, Self::Position { .. }) { |
| 129 | // OneNote gives a page its margin origin when an outline on it first moves. | ||
| 130 | let page = changed.get_mut(page).unwrap(); | ||
| 131 | if !crate::PropertySets::parse(&page.bytes)?.sets[0] | ||
| 132 | .iter() | ||
| 133 | .any(|field| field.id == 0x14001d0f) | ||
| 134 | { | ||
| 135 | page.set(&[ | ||
| 136 | (0x14001d0f, &1.0_f32.to_le_bytes()), | ||
| 137 | (0x14001d10, &0.4_f32.to_le_bytes()), | ||
| 138 | ])?; | ||
| 139 | } | ||
| 129 | update_title(active, view.clone(), &mut changed)?; | 140 | update_title(active, view.clone(), &mut changed)?; |
| 130 | } | 141 | } |
| 131 | Ok(changed) | 142 | Ok(changed) |
crates/onestore/src/page/ink.rs+1-1| ... | @@ -50,7 +50,7 @@ pub struct InkStroke { | ... | @@ -50,7 +50,7 @@ pub struct InkStroke { |
| 50 | } | 50 | } |
| 51 | 51 | ||
| 52 | impl Ink { | 52 | impl Ink { |
| 53 | pub(super) fn read( | 53 | pub(crate) fn read( |
| 54 | revision: &Revision<'_>, | 54 | revision: &Revision<'_>, |
| 55 | id: ExGuid, | 55 | id: ExGuid, |
| 56 | node: &crate::document::Element<'_>, | 56 | node: &crate::document::Element<'_>, |
crates/onestore/src/page/mod.rs+96-6| ... | @@ -209,7 +209,7 @@ impl PartialEq for Attachment { | ... | @@ -209,7 +209,7 @@ impl PartialEq for Attachment { |
| 209 | } | 209 | } |
| 210 | 210 | ||
| 211 | impl Attachment { | 211 | impl Attachment { |
| 212 | fn read( | 212 | pub(crate) fn read( |
| 213 | revision: &Revision<'_>, | 213 | revision: &Revision<'_>, |
| 214 | id: ExGuid, | 214 | id: ExGuid, |
| 215 | node: &crate::document::Element<'_>, | 215 | node: &crate::document::Element<'_>, |
| ... | @@ -320,7 +320,7 @@ impl PartialEq for Image { | ... | @@ -320,7 +320,7 @@ impl PartialEq for Image { |
| 320 | } | 320 | } |
| 321 | 321 | ||
| 322 | impl Image { | 322 | impl Image { |
| 323 | fn read( | 323 | pub(crate) fn read( |
| 324 | revision: &Revision<'_>, | 324 | revision: &Revision<'_>, |
| 325 | id: ExGuid, | 325 | id: ExGuid, |
| 326 | node: &crate::document::Element<'_>, | 326 | node: &crate::document::Element<'_>, |
| ... | @@ -556,9 +556,11 @@ impl Page { | ... | @@ -556,9 +556,11 @@ impl Page { |
| 556 | })?)) | 556 | })?)) |
| 557 | }) | 557 | }) |
| 558 | .transpose()?, | 558 | .transpose()?, |
| 559 | // OneNote 2010 snaps to, and on the first move writes, (36, 14.4) when unset: 1.0 | ||
| 560 | // and 0.4 half inches, read as stored ones are. | ||
| 559 | margin_origin: [ | 561 | margin_origin: [ |
| 560 | margin_origin_x.unwrap_or(0.0), | 562 | margin_origin_x.unwrap_or(1.0_f32 * 36.0), |
| 561 | margin_origin_y.unwrap_or(0.0), | 563 | margin_origin_y.unwrap_or(0.4_f32 * 36.0), |
| 562 | ], | 564 | ], |
| 563 | objects: Vec::new(), | 565 | objects: Vec::new(), |
| 564 | definitions: BTreeMap::new(), | 566 | definitions: BTreeMap::new(), |
| ... | @@ -633,8 +635,8 @@ impl Page { | ... | @@ -633,8 +635,8 @@ impl Page { |
| 633 | }; | 635 | }; |
| 634 | let mut outline = Outline { | 636 | let mut outline = Outline { |
| 635 | id, | 637 | id, |
| 636 | // OneNote flags the outline and its paragraph; `PageCreation` flags only | 638 | // OneNote flags the outline and its paragraph, and still shows an outline |
| 637 | // the paragraph, and OneNote still shows that outline as the title. | 639 | // whose paragraph alone is flagged as the title. |
| 638 | title: title_text(node) | 640 | title: title_text(node) |
| 639 | || node | 641 | || node |
| 640 | .children | 642 | .children |
| ... | @@ -682,6 +684,94 @@ impl Page { | ... | @@ -682,6 +684,94 @@ impl Page { |
| 682 | } | 684 | } |
| 683 | } | 685 | } |
| 684 | 686 | ||
| 687 | /// The format `container` passes to its children, as reading its page inherits it. | ||
| 688 | fn inherited_by( | ||
| 689 | revision: &Revision<'_>, | ||
| 690 | parents: &BTreeMap<ExGuid, Vec<ExGuid>>, | ||
| 691 | container: ExGuid, | ||
| 692 | ) -> Result<Format, Error> { | ||
| 693 | let invalid = |message| Error { offset: 0, message }; | ||
| 694 | let node = revision | ||
| 695 | .nodes | ||
| 696 | .get(&container) | ||
| 697 | .ok_or_else(|| invalid("Missing canvas outline object"))?; | ||
| 698 | let parent = || -> Result<ExGuid, Error> { | ||
| 699 | match parents.get(&container).map(Vec::as_slice) { | ||
| 700 | Some([parent]) => Ok(*parent), | ||
| 701 | _ => Err(invalid("Page content must have one parent")), | ||
| 702 | } | ||
| 703 | }; | ||
| 704 | Ok(match &node.kind { | ||
| 705 | Kind::Outline { .. } => node.format.clone(), | ||
| 706 | Kind::OutlineGroup => node | ||
| 707 | .format | ||
| 708 | .inherit(&inherited_by(revision, parents, parent()?)?), | ||
| 709 | Kind::Paragraph { | ||
| 710 | paragraph_style, .. | ||
| 711 | } => { | ||
| 712 | let style = match paragraph_style { | ||
| 713 | Some(id) => revision.nodes.get(id).map(|n| n.format.clone()).unwrap_or_default(), | ||
| 714 | None => Format::default(), | ||
| 715 | }; | ||
| 716 | node.format | ||
| 717 | .inherit(&style) | ||
| 718 | .inherit(&inherited_by(revision, parents, parent()?)?) | ||
| 719 | } | ||
| 720 | Kind::Cell { .. } => { | ||
| 721 | let row = parent()?; | ||
| 722 | let [table] = parents.get(&row).map(Vec::as_slice).unwrap_or_default() else { | ||
| 723 | return Err(invalid("A table row must have one table")); | ||
| 724 | }; | ||
| 725 | let [holder] = parents.get(table).map(Vec::as_slice).unwrap_or_default() else { | ||
| 726 | return Err(invalid("A table must have one paragraph")); | ||
| 727 | }; | ||
| 728 | node.format.inherit( | ||
| 729 | &revision.nodes[table] | ||
| 730 | .format | ||
| 731 | .inherit(&inherited_by(revision, parents, *holder)?), | ||
| 732 | ) | ||
| 733 | } | ||
| 734 | _ => Format::default(), | ||
| 735 | }) | ||
| 736 | } | ||
| 737 | |||
| 738 | /// The text of rich-text object `text` as its page model shows it, read from its ancestors | ||
| 739 | /// alone. | ||
| 740 | pub(crate) fn text_of( | ||
| 741 | revision: &Revision<'_>, | ||
| 742 | parents: &BTreeMap<ExGuid, Vec<ExGuid>>, | ||
| 743 | text: ExGuid, | ||
| 744 | ) -> Result<Paragraph, Error> { | ||
| 745 | let invalid = |message| Error { offset: 0, message }; | ||
| 746 | let [paragraph] = parents.get(&text).map(Vec::as_slice).unwrap_or_default() else { | ||
| 747 | return Err(invalid("Select text belonging to one paragraph")); | ||
| 748 | }; | ||
| 749 | let format = inherited_by(revision, parents, *paragraph)?; | ||
| 750 | let node = revision | ||
| 751 | .nodes | ||
| 752 | .get(&text) | ||
| 753 | .ok_or_else(|| invalid("Missing text object"))?; | ||
| 754 | let Kind::RichText { | ||
| 755 | paragraph_style, .. | ||
| 756 | } = &node.kind | ||
| 757 | else { | ||
| 758 | return Err(invalid("Select a rich-text object")); | ||
| 759 | }; | ||
| 760 | let runs = revision.text_runs(text)?; | ||
| 761 | Ok(if runs.is_empty() { | ||
| 762 | let style = match paragraph_style { | ||
| 763 | Some(id) => revision.nodes.get(id).map(|n| n.format.clone()).unwrap_or_default(), | ||
| 764 | None => Format::default(), | ||
| 765 | }; | ||
| 766 | Paragraph::new(String::new(), node.format.inherit(&style).inherit(&format)) | ||
| 767 | } else { | ||
| 768 | Paragraph::from_runs( | ||
| 769 | runs.into_iter() | ||
| 770 | .map(|run| (run.text.to_owned(), run.format.inherit(&format))), | ||
| 771 | ) | ||
| 772 | }) | ||
| 773 | } | ||
| 774 | |||
| 685 | fn read_paragraphs( | 775 | fn read_paragraphs( |
| 686 | revision: &Revision<'_>, | 776 | revision: &Revision<'_>, |
| 687 | container: &Element<'_>, | 777 | container: &Element<'_>, |
crates/onestore/src/page/write.rs+368-1803| ... | @@ -1,38 +1,29 @@ | ... | @@ -1,38 +1,29 @@ |
| 1 | //! Publishes an edited page model by lowering the difference from the stored page onto | 1 | //! Publishes an edited page model by lowering the difference from the stored page onto |
| 2 | //! the typed writers, whose revisions accumulate in memory, then squashing them into one | 2 | //! the typed writers, whose revisions accumulate in memory, then squashing them into one |
| 3 | //! revision per space. | 3 | //! revision per space. `PreparedEdit::page` keeps it until the op path replaces it. |
| 4 | 4 | ||
| 5 | use super::{ | 5 | use super::{Image, Ink, Outline, Page, PageObject, PageParagraph, ParagraphContent}; |
| 6 | Attachment, Image, Ink, Math, Outline, Page, PageObject, PageParagraph, ParagraphContent, Table, | ||
| 7 | }; | ||
| 8 | use crate::{ | 6 | use crate::{ |
| 9 | Error, ExGuid, Insertion, ObjectData, OutlineEdit, ParagraphJoin, ParagraphSplit, PropertySets, | 7 | Error, ExGuid, Insertion, ObjectData, OutlineEdit, ParagraphJoin, ParagraphSplit, PropertySets, |
| 10 | RevisionIndex, Store, TextAttribute, TreeEdit, Value, | 8 | RevisionIndex, Store, TreeEdit, Value, |
| 11 | active::{ActivePage, Changes}, | 9 | active::{ActivePage, Changes}, |
| 12 | document::{Format, Kind, Tag}, | 10 | document::Kind, |
| 11 | op::{ | ||
| 12 | content::{self, AttachmentIds}, | ||
| 13 | levels, | ||
| 14 | lower::{View, collect_containers, kept_set, text_edit, validate}, | ||
| 15 | properties, | ||
| 16 | table::{self, Structure}, | ||
| 17 | }, | ||
| 13 | write::{PropertyObject, RevisionEdit}, | 18 | write::{PropertyObject, RevisionEdit}, |
| 14 | }; | 19 | }; |
| 15 | use bumpalo::Bump; | 20 | use bumpalo::Bump; |
| 16 | use std::{ | 21 | use std::collections::{BTreeMap, BTreeSet}; |
| 17 | collections::{BTreeMap, BTreeSet}, | ||
| 18 | ops::Range, | ||
| 19 | }; | ||
| 20 | |||
| 21 | /// Property identifiers with their encoded values. | ||
| 22 | type Values = Vec<(u32, Vec<u8>)>; | ||
| 23 | 22 | ||
| 24 | fn invalid(message: &'static str) -> Error { | 23 | fn invalid(message: &'static str) -> Error { |
| 25 | Error { offset: 0, message } | 24 | Error { offset: 0, message } |
| 26 | } | 25 | } |
| 27 | 26 | ||
| 28 | /// A native measurement array: a count byte, `header - 1` reserved bytes, then each value | ||
| 29 | /// in inches. | ||
| 30 | /// The `<ifndf>{GUID}` form a file-data declaration uses to name a payload in the file-data store. | ||
| 31 | fn payload_reference(guid: [u8; 16]) -> String { | ||
| 32 | let id = ExGuid { guid, n: 0 }.to_string(); | ||
| 33 | format!("<ifndf>{}", id.split(',').next().unwrap()) | ||
| 34 | } | ||
| 35 | |||
| 36 | fn page_images(page: &Page) -> impl Iterator<Item = (ExGuid, &Image)> { | 27 | fn page_images(page: &Page) -> impl Iterator<Item = (ExGuid, &Image)> { |
| 37 | page.objects.iter().filter_map(|object| match object { | 28 | page.objects.iter().filter_map(|object| match object { |
| 38 | PageObject::Image(image) => Some((image.id, image)), | 29 | PageObject::Image(image) => Some((image.id, image)), |
| ... | @@ -47,292 +38,6 @@ fn page_ink(page: &Page) -> impl Iterator<Item = (ExGuid, &Ink)> { | ... | @@ -47,292 +38,6 @@ fn page_ink(page: &Page) -> impl Iterator<Item = (ExGuid, &Ink)> { |
| 47 | }) | 38 | }) |
| 48 | } | 39 | } |
| 49 | 40 | ||
| 50 | /// The compact child references a page object holds. | ||
| 51 | fn page_children(object: &PropertyObject) -> Result<Vec<u8>, Error> { | ||
| 52 | let properties = crate::PropertySets::parse(&object.bytes)?; | ||
| 53 | match properties.sets[0] | ||
| 54 | .iter() | ||
| 55 | .find(|p| p.id == 0x24001c20) | ||
| 56 | .map(|p| &p.value) | ||
| 57 | { | ||
| 58 | Some(crate::Value::References { compact_ids, .. }) => Ok(compact_ids.to_vec()), | ||
| 59 | None => Ok(Vec::new()), | ||
| 60 | _ => Err(invalid("The page has an invalid child list")), | ||
| 61 | } | ||
| 62 | } | ||
| 63 | |||
| 64 | /// Creates stroke objects (numbered from `first`) and one drawing-attribute object per | ||
| 65 | /// distinct pen; returns the compact references for the data node's stroke list. | ||
| 66 | fn write_strokes( | ||
| 67 | changed: &mut BTreeMap<ExGuid, PropertyObject>, | ||
| 68 | data_object: &mut PropertyObject, | ||
| 69 | strokes: &[(ExGuid, &super::InkStroke)], | ||
| 70 | first: usize, | ||
| 71 | filetime: u64, | ||
| 72 | ) -> Result<Vec<u8>, Error> { | ||
| 73 | let mut styles: Vec<(InkPen, ExGuid)> = Vec::new(); | ||
| 74 | let mut references = Vec::new(); | ||
| 75 | for (offset, (stroke_id, stroke)) in strokes.iter().enumerate() { | ||
| 76 | let pen = InkPen::of(stroke); | ||
| 77 | let style = match styles.iter().find(|(known, _)| *known == pen) { | ||
| 78 | Some((_, id)) => *id, | ||
| 79 | None => { | ||
| 80 | let id = ExGuid { | ||
| 81 | guid: crate::write::fresh_guid()?, | ||
| 82 | n: 1, | ||
| 83 | }; | ||
| 84 | let mut style = PropertyObject { | ||
| 85 | jcid: 0x120048, | ||
| 86 | bytes: crate::create::properties(&pen.values())?, | ||
| 87 | global_ids: std::sync::Arc::new(BTreeMap::from([(0, id.guid)])), | ||
| 88 | }; | ||
| 89 | style.reference(id)?; | ||
| 90 | changed.insert(id, style); | ||
| 91 | styles.push((pen, id)); | ||
| 92 | id | ||
| 93 | } | ||
| 94 | }; | ||
| 95 | let mut object = PropertyObject { | ||
| 96 | jcid: 0x20047, | ||
| 97 | bytes: crate::create::properties(&stroke_values( | ||
| 98 | stroke, | ||
| 99 | (first + offset) as u32 + 1, | ||
| 100 | filetime, | ||
| 101 | )?)?, | ||
| 102 | global_ids: std::sync::Arc::new(BTreeMap::from([(0, stroke_id.guid)])), | ||
| 103 | }; | ||
| 104 | object.reference(*stroke_id)?; | ||
| 105 | let style_reference = object.reference(style)?; | ||
| 106 | object.set(&[(0x20003409, &style_reference)])?; | ||
| 107 | changed.insert(*stroke_id, object); | ||
| 108 | references.extend(data_object.reference(*stroke_id)?); | ||
| 109 | } | ||
| 110 | Ok(references) | ||
| 111 | } | ||
| 112 | |||
| 113 | /// The drawing attributes OneNote shares between strokes drawn with the same pen. | ||
| 114 | #[derive(PartialEq)] | ||
| 115 | struct InkPen { | ||
| 116 | width: u32, | ||
| 117 | height: u32, | ||
| 118 | color: Option<u32>, | ||
| 119 | transparency: Option<u8>, | ||
| 120 | pen_tip: Option<u8>, | ||
| 121 | } | ||
| 122 | |||
| 123 | impl InkPen { | ||
| 124 | fn of(stroke: &super::InkStroke) -> Self { | ||
| 125 | Self { | ||
| 126 | width: (stroke.width * 2540.0 / 72.0).to_bits(), | ||
| 127 | height: (stroke.height * 2540.0 / 72.0).to_bits(), | ||
| 128 | color: stroke.color, | ||
| 129 | transparency: stroke.transparency, | ||
| 130 | pen_tip: stroke.pen_tip, | ||
| 131 | } | ||
| 132 | } | ||
| 133 | |||
| 134 | fn values(&self) -> Values { | ||
| 135 | let mut values: Values = vec![ | ||
| 136 | (0x1c00340a, super::ink::DIMENSIONS.to_vec()), | ||
| 137 | (0x1400340c, self.height.to_le_bytes().to_vec()), | ||
| 138 | (0x1400340d, self.width.to_le_bytes().to_vec()), | ||
| 139 | ]; | ||
| 140 | if let Some(color) = self.color { | ||
| 141 | values.push((0x1400340f, color.to_le_bytes().to_vec())); | ||
| 142 | } | ||
| 143 | if let Some(transparency) = self.transparency { | ||
| 144 | values.push((0x0c003414, vec![transparency])); | ||
| 145 | } | ||
| 146 | if let Some(tip) = self.pen_tip { | ||
| 147 | values.push((0x0c003412, vec![tip])); | ||
| 148 | } | ||
| 149 | values | ||
| 150 | } | ||
| 151 | } | ||
| 152 | |||
| 153 | fn stroke_values(stroke: &super::InkStroke, index: u32, filetime: u64) -> Result<Values, Error> { | ||
| 154 | if stroke.points.is_empty() { | ||
| 155 | return Err(invalid("A stroke needs at least one point")); | ||
| 156 | } | ||
| 157 | if stroke | ||
| 158 | .points | ||
| 159 | .iter() | ||
| 160 | .any(|p| !p[0].is_finite() || !p[1].is_finite()) | ||
| 161 | || !(stroke.width.is_finite() && stroke.height.is_finite()) | ||
| 162 | || stroke.width <= 0.0 | ||
| 163 | || stroke.height <= 0.0 | ||
| 164 | { | ||
| 165 | return Err(invalid( | ||
| 166 | "Stroke points and pen size must be finite and positive", | ||
| 167 | )); | ||
| 168 | } | ||
| 169 | let left = stroke | ||
| 170 | .points | ||
| 171 | .iter() | ||
| 172 | .map(|p| p[0]) | ||
| 173 | .fold(f32::INFINITY, f32::min); | ||
| 174 | let top = stroke | ||
| 175 | .points | ||
| 176 | .iter() | ||
| 177 | .map(|p| p[1]) | ||
| 178 | .fold(f32::INFINITY, f32::min); | ||
| 179 | let mut origin = ((left - stroke.width / 2.0) / 36.0).to_le_bytes().to_vec(); | ||
| 180 | origin.extend_from_slice(&((top - stroke.height / 2.0) / 36.0).to_le_bytes()); | ||
| 181 | Ok(vec![ | ||
| 182 | (0x1c00340b, stroke.packet()), | ||
| 183 | (0x14003419, index.to_le_bytes().to_vec()), | ||
| 184 | (0x1000341b, 0x409_u16.to_le_bytes().to_vec()), | ||
| 185 | (0x0c00341c, vec![0]), | ||
| 186 | (0x1c00341a, crate::write::fresh_guid()?.to_vec()), | ||
| 187 | (0x1c00341d, filetime.to_le_bytes().to_vec()), | ||
| 188 | (0x1c00345b, origin), | ||
| 189 | ]) | ||
| 190 | } | ||
| 191 | |||
| 192 | /// The character-style properties a span format sets; paragraph-level fields stay on the | ||
| 193 | /// paragraph style. | ||
| 194 | fn style_values(format: &crate::document::Format) -> Values { | ||
| 195 | let mut values = Values::new(); | ||
| 196 | for (id, flag) in [ | ||
| 197 | (0x08001c04, format.bold), | ||
| 198 | (0x08001c05, format.italic), | ||
| 199 | (0x08001c06, format.underline), | ||
| 200 | (0x08001c07, format.strike), | ||
| 201 | (0x08001c08, format.superscript), | ||
| 202 | (0x08001c09, format.subscript), | ||
| 203 | (0x08001e16, format.hidden), | ||
| 204 | (0x08001e14, format.hyperlink), | ||
| 205 | (0x08001e19, format.hyperlink_label), | ||
| 206 | (0x08003401, format.math), | ||
| 207 | (0x08001e22, format.embedded_object), | ||
| 208 | ] { | ||
| 209 | if let Some(flag) = flag { | ||
| 210 | values.push((id | (u32::from(flag) << 31), Vec::new())); | ||
| 211 | } | ||
| 212 | } | ||
| 213 | if let Some(font) = &format.font { | ||
| 214 | values.push((0x1c001c0a, crate::create::string(font))); | ||
| 215 | } | ||
| 216 | if let Some(size) = format.font_size { | ||
| 217 | values.push(( | ||
| 218 | 0x10001c0b, | ||
| 219 | ((size * 2.0).round() as u16).to_le_bytes().to_vec(), | ||
| 220 | )); | ||
| 221 | } | ||
| 222 | if let Some(color) = format.color { | ||
| 223 | values.push((0x14001c0c, color.to_le_bytes().to_vec())); | ||
| 224 | } | ||
| 225 | if let Some(highlight) = format.highlight { | ||
| 226 | values.push((0x14001c0d, highlight.to_le_bytes().to_vec())); | ||
| 227 | } | ||
| 228 | if let Some(language) = format.language { | ||
| 229 | values.push((0x14001c3b, language.to_le_bytes().to_vec())); | ||
| 230 | } | ||
| 231 | values | ||
| 232 | } | ||
| 233 | |||
| 234 | fn picture_fixed_fields(stored: &Image, image: &Image) -> Result<(), Error> { | ||
| 235 | if stored.id != image.id | ||
| 236 | || stored.bytes != image.bytes | ||
| 237 | || stored.size != image.size | ||
| 238 | || stored.background != image.background | ||
| 239 | { | ||
| 240 | return Err(invalid( | ||
| 241 | "A stored picture keeps its payload, intrinsic size and background state", | ||
| 242 | )); | ||
| 243 | } | ||
| 244 | Ok(()) | ||
| 245 | } | ||
| 246 | |||
| 247 | /// Position and displayed-size properties that take `layout` from `stored`, and the ones | ||
| 248 | /// to remove. | ||
| 249 | fn layout_values( | ||
| 250 | stored: &crate::document::Layout, | ||
| 251 | layout: &crate::document::Layout, | ||
| 252 | ) -> Result<(Values, Vec<u32>), Error> { | ||
| 253 | let mut values = Values::new(); | ||
| 254 | let mut removed = Vec::new(); | ||
| 255 | if (layout.x, layout.y) != (stored.x, stored.y) { | ||
| 256 | let (Some(x), Some(y)) = (layout.x, layout.y) else { | ||
| 257 | return Err(invalid("A picture position needs both coordinates")); | ||
| 258 | }; | ||
| 259 | if !(x.is_finite() && y.is_finite()) { | ||
| 260 | return Err(invalid("A picture position must be finite")); | ||
| 261 | } | ||
| 262 | values.push((0x14001c14, (x / 36.0).to_le_bytes().to_vec())); | ||
| 263 | values.push((0x14001c15, (y / 36.0).to_le_bytes().to_vec())); | ||
| 264 | } | ||
| 265 | if ( | ||
| 266 | layout.max_width, | ||
| 267 | layout.max_height, | ||
| 268 | layout.width_set_by_user, | ||
| 269 | ) != ( | ||
| 270 | stored.max_width, | ||
| 271 | stored.max_height, | ||
| 272 | stored.width_set_by_user, | ||
| 273 | ) { | ||
| 274 | match (layout.max_width, layout.max_height) { | ||
| 275 | (Some(width), Some(height)) => { | ||
| 276 | if !(width.is_finite() && height.is_finite() && width > 0.0 && height > 0.0) { | ||
| 277 | return Err(invalid("Picture size must be positive")); | ||
| 278 | } | ||
| 279 | values.push((0x14001c1b, (width / 36.0).to_le_bytes().to_vec())); | ||
| 280 | values.push((0x14001c1c, (height / 36.0).to_le_bytes().to_vec())); | ||
| 281 | let user_set = u32::from(layout.width_set_by_user == Some(true)); | ||
| 282 | values.push((0x08001cbd | (user_set << 31), Vec::new())); | ||
| 283 | } | ||
| 284 | (None, None) => removed.extend([0x14001c1b, 0x14001c1c, 0x08001cbd]), | ||
| 285 | _ => return Err(invalid("A picture size needs both dimensions")), | ||
| 286 | } | ||
| 287 | } | ||
| 288 | if layout.reserved_width != stored.reserved_width { | ||
| 289 | return Err(invalid("A picture has no reserved width")); | ||
| 290 | } | ||
| 291 | Ok((values, removed)) | ||
| 292 | } | ||
| 293 | |||
| 294 | fn measurement_bytes(values: &[f32], header: usize) -> Result<Vec<u8>, Error> { | ||
| 295 | let count = u8::try_from(values.len()) | ||
| 296 | .map_err(|_| invalid("A measurement array exceeds the document range"))?; | ||
| 297 | let mut bytes = vec![0u8; header]; | ||
| 298 | bytes[0] = count; | ||
| 299 | for value in values { | ||
| 300 | if !value.is_finite() { | ||
| 301 | return Err(invalid("A measurement must be finite")); | ||
| 302 | } | ||
| 303 | bytes.extend_from_slice(&(value / 36.0).to_le_bytes()); | ||
| 304 | } | ||
| 305 | Ok(bytes) | ||
| 306 | } | ||
| 307 | |||
| 308 | /// Every row carries one cell per column and widths are usable. | ||
| 309 | fn validate_table(table: &Table) -> Result<(), Error> { | ||
| 310 | if table.rows.is_empty() || table.columns.is_empty() { | ||
| 311 | return Err(invalid("A table needs at least one row and one column")); | ||
| 312 | } | ||
| 313 | if table | ||
| 314 | .columns | ||
| 315 | .iter() | ||
| 316 | .any(|c| !c.width.is_finite() || c.width < 36.0) | ||
| 317 | { | ||
| 318 | return Err(invalid("Table columns are at least 36 points wide")); | ||
| 319 | } | ||
| 320 | for row in &table.rows { | ||
| 321 | if row.cells.len() != table.columns.len() { | ||
| 322 | return Err(invalid("Every table row has one cell per column")); | ||
| 323 | } | ||
| 324 | for cell in &row.cells { | ||
| 325 | // An emptied cell keeps a replacement paragraph, as the tree writer provides. | ||
| 326 | for paragraph in &cell.paragraphs { | ||
| 327 | if let ParagraphContent::Table(nested) = &paragraph.content { | ||
| 328 | validate_table(nested)?; | ||
| 329 | } | ||
| 330 | } | ||
| 331 | } | ||
| 332 | } | ||
| 333 | Ok(()) | ||
| 334 | } | ||
| 335 | |||
| 336 | pub(crate) fn write_page( | 41 | pub(crate) fn write_page( |
| 337 | source: &[u8], | 42 | source: &[u8], |
| 338 | space: ExGuid, | 43 | space: ExGuid, |
| ... | @@ -374,93 +79,6 @@ pub(crate) fn write_page( | ... | @@ -374,93 +79,6 @@ pub(crate) fn write_page( |
| 374 | ) | 79 | ) |
| 375 | } | 80 | } |
| 376 | 81 | ||
| 377 | /// Direct children of every container, in model order, plus lookups by identity. | ||
| 378 | struct View<'a> { | ||
| 379 | page: &'a Page, | ||
| 380 | outlines: BTreeMap<ExGuid, &'a Outline>, | ||
| 381 | /// Outlines owned by a title object rather than the page. | ||
| 382 | title_outlines: BTreeSet<ExGuid>, | ||
| 383 | paragraphs: BTreeMap<ExGuid, &'a PageParagraph>, | ||
| 384 | children: BTreeMap<ExGuid, Vec<ExGuid>>, | ||
| 385 | container: BTreeMap<ExGuid, ExGuid>, | ||
| 386 | page_children: Vec<ExGuid>, | ||
| 387 | } | ||
| 388 | |||
| 389 | impl<'a> View<'a> { | ||
| 390 | fn new(page: &'a Page) -> Result<Self, Error> { | ||
| 391 | let mut view = Self { | ||
| 392 | page, | ||
| 393 | outlines: BTreeMap::new(), | ||
| 394 | title_outlines: BTreeSet::new(), | ||
| 395 | paragraphs: BTreeMap::new(), | ||
| 396 | children: BTreeMap::new(), | ||
| 397 | container: BTreeMap::new(), | ||
| 398 | page_children: Vec::new(), | ||
| 399 | }; | ||
| 400 | for object in &page.objects { | ||
| 401 | view.page_children.push(object.id()); | ||
| 402 | match object { | ||
| 403 | PageObject::Outline(outline) => view.outline(outline)?, | ||
| 404 | PageObject::Title(title) => { | ||
| 405 | for outline in &title.outlines { | ||
| 406 | view.title_outlines.insert(outline.id); | ||
| 407 | view.outline(outline)?; | ||
| 408 | } | ||
| 409 | } | ||
| 410 | PageObject::Image(_) | PageObject::Ink(_) | PageObject::Unsupported(_) => {} | ||
| 411 | } | ||
| 412 | } | ||
| 413 | Ok(view) | ||
| 414 | } | ||
| 415 | |||
| 416 | fn outline(&mut self, outline: &'a Outline) -> Result<(), Error> { | ||
| 417 | if self.outlines.insert(outline.id, outline).is_some() { | ||
| 418 | return Err(invalid("The page model repeats an outline identity")); | ||
| 419 | } | ||
| 420 | self.children.entry(outline.id).or_default(); | ||
| 421 | self.paragraphs_of(outline.id, &outline.paragraphs) | ||
| 422 | } | ||
| 423 | |||
| 424 | fn paragraphs_of(&mut self, root: ExGuid, list: &'a [PageParagraph]) -> Result<(), Error> { | ||
| 425 | for paragraph in list { | ||
| 426 | let container = paragraph.parent.unwrap_or(root); | ||
| 427 | if paragraph | ||
| 428 | .parent | ||
| 429 | .is_some_and(|parent| !self.paragraphs.contains_key(&parent)) | ||
| 430 | { | ||
| 431 | return Err(invalid( | ||
| 432 | "A paragraph's parent must precede it in its container", | ||
| 433 | )); | ||
| 434 | } | ||
| 435 | if self.paragraphs.insert(paragraph.id, paragraph).is_some() { | ||
| 436 | return Err(invalid("The page model repeats a paragraph identity")); | ||
| 437 | } | ||
| 438 | self.children | ||
| 439 | .entry(container) | ||
| 440 | .or_default() | ||
| 441 | .push(paragraph.id); | ||
| 442 | self.children.entry(paragraph.id).or_default(); | ||
| 443 | self.container.insert(paragraph.id, container); | ||
| 444 | if let ParagraphContent::Table(table) = &paragraph.content { | ||
| 445 | for row in &table.rows { | ||
| 446 | for cell in &row.cells { | ||
| 447 | if self.children.contains_key(&cell.id) { | ||
| 448 | return Err(invalid("The page model repeats a cell identity")); | ||
| 449 | } | ||
| 450 | self.children.entry(cell.id).or_default(); | ||
| 451 | self.paragraphs_of(cell.id, &cell.paragraphs)?; | ||
| 452 | } | ||
| 453 | } | ||
| 454 | } | ||
| 455 | } | ||
| 456 | Ok(()) | ||
| 457 | } | ||
| 458 | |||
| 459 | fn text(&self, paragraph: ExGuid) -> Option<&'a super::TextObject> { | ||
| 460 | self.paragraphs.get(&paragraph).and_then(|p| p.text()) | ||
| 461 | } | ||
| 462 | } | ||
| 463 | |||
| 464 | struct Lowering<'a> { | 82 | struct Lowering<'a> { |
| 465 | active: ActivePage<'a>, | 83 | active: ActivePage<'a>, |
| 466 | /// Holds the objects the typed writers store for as long as `active` reads them. | 84 | /// Holds the objects the typed writers store for as long as `active` reads them. |
| ... | @@ -521,30 +139,9 @@ impl<'a> Lowering<'a> { | ... | @@ -521,30 +139,9 @@ impl<'a> Lowering<'a> { |
| 521 | } | 139 | } |
| 522 | 140 | ||
| 523 | fn run(&mut self, before: &Page, after: &Page) -> Result<(), Error> { | 141 | fn run(&mut self, before: &Page, after: &Page) -> Result<(), Error> { |
| 524 | if after.created != before.created || after.margin_origin != before.margin_origin { | 142 | let old = View::new(before, false)?; |
| 525 | return Err(invalid("Page creation time and margins cannot be edited")); | 143 | let new = View::new(after, false)?; |
| 526 | } | 144 | validate(before, after, &old, &new)?; |
| 527 | for (id, definition) in &after.definitions { | ||
| 528 | if before | ||
| 529 | .definitions | ||
| 530 | .get(id) | ||
| 531 | .is_some_and(|stored| stored != definition) | ||
| 532 | && !matches!( | ||
| 533 | definition.kind, | ||
| 534 | Kind::List { .. } | Kind::TagDefinition { .. } | ||
| 535 | ) | ||
| 536 | { | ||
| 537 | return Err(invalid("Style definitions cannot be edited")); | ||
| 538 | } | ||
| 539 | } | ||
| 540 | let old = View::new(before)?; | ||
| 541 | let new = View::new(after)?; | ||
| 542 | for paragraph in new.paragraphs.values() { | ||
| 543 | if let ParagraphContent::Table(table) = &paragraph.content { | ||
| 544 | validate_table(table)?; | ||
| 545 | } | ||
| 546 | } | ||
| 547 | self.check_fixed_objects(before, after, &old, &new)?; | ||
| 548 | for id in new.outlines.keys().chain(new.paragraphs.keys()) { | 145 | for id in new.outlines.keys().chain(new.paragraphs.keys()) { |
| 549 | if !old.outlines.contains_key(id) | 146 | if !old.outlines.contains_key(id) |
| 550 | && !old.paragraphs.contains_key(id) | 147 | && !old.paragraphs.contains_key(id) |
| ... | @@ -567,9 +164,12 @@ impl<'a> Lowering<'a> { | ... | @@ -567,9 +164,12 @@ impl<'a> Lowering<'a> { |
| 567 | .collect(); | 164 | .collect(); |
| 568 | self.insert_outlines(&old, &new, &mut placed, &mut page_order)?; | 165 | self.insert_outlines(&old, &new, &mut placed, &mut page_order)?; |
| 569 | let mut consumed = BTreeSet::new(); | 166 | let mut consumed = BTreeSet::new(); |
| 167 | self.ungroup(&old, &new)?; | ||
| 570 | self.split_and_join(&old, &new, &mut placed, &mut consumed)?; | 168 | self.split_and_join(&old, &new, &mut placed, &mut consumed)?; |
| 571 | self.place(&old, &new, &placed, &page_order)?; | 169 | self.place(&old, &new, &placed, &page_order)?; |
| 572 | self.delete(&old, &new, &consumed)?; | 170 | self.delete(&old, &new, &consumed)?; |
| 171 | self.edit_levels(&new)?; | ||
| 172 | self.edit_text_identities(&new)?; | ||
| 573 | self.edit_equations(&new)?; | 173 | self.edit_equations(&new)?; |
| 574 | self.edit_text(&new)?; | 174 | self.edit_text(&new)?; |
| 575 | self.edit_paragraph_styles(&old, &new, after)?; | 175 | self.edit_paragraph_styles(&old, &new, after)?; |
| ... | @@ -581,123 +181,6 @@ impl<'a> Lowering<'a> { | ... | @@ -581,123 +181,6 @@ impl<'a> Lowering<'a> { |
| 581 | Ok(()) | 181 | Ok(()) |
| 582 | } | 182 | } |
| 583 | 183 | ||
| 584 | fn check_fixed_objects( | ||
| 585 | &self, | ||
| 586 | before: &Page, | ||
| 587 | after: &Page, | ||
| 588 | old: &View<'_>, | ||
| 589 | new: &View<'_>, | ||
| 590 | ) -> Result<(), Error> { | ||
| 591 | let fixed = |page: &Page| -> Vec<String> { | ||
| 592 | page.objects | ||
| 593 | .iter() | ||
| 594 | .filter_map(|object| match object { | ||
| 595 | PageObject::Unsupported(unsupported) => Some(format!("{unsupported:?}")), | ||
| 596 | PageObject::Title(title) => Some(format!( | ||
| 597 | "{:?} {:?} {:?} {:?}", | ||
| 598 | title.id, | ||
| 599 | title.date, | ||
| 600 | title.layout, | ||
| 601 | title.outlines.iter().map(|o| o.id).collect::<Vec<_>>() | ||
| 602 | )), | ||
| 603 | PageObject::Outline(_) | PageObject::Image(_) | PageObject::Ink(_) => None, | ||
| 604 | }) | ||
| 605 | .collect() | ||
| 606 | }; | ||
| 607 | let mut before_fixed = fixed(before); | ||
| 608 | let mut after_fixed = fixed(after); | ||
| 609 | before_fixed.sort(); | ||
| 610 | after_fixed.sort(); | ||
| 611 | if before_fixed != after_fixed { | ||
| 612 | return Err(invalid( | ||
| 613 | "Titles and unsupported objects cannot be edited through the page model", | ||
| 614 | )); | ||
| 615 | } | ||
| 616 | for (id, outline) in &new.outlines { | ||
| 617 | if outline.paragraphs.is_empty() { | ||
| 618 | return Err(invalid( | ||
| 619 | "An outline needs a paragraph; remove the outline instead", | ||
| 620 | )); | ||
| 621 | } | ||
| 622 | let Some(previous) = old.outlines.get(id) else { | ||
| 623 | continue; | ||
| 624 | }; | ||
| 625 | let same = outline.title == previous.title | ||
| 626 | && outline.min_width == previous.min_width | ||
| 627 | && outline.indents == previous.indents | ||
| 628 | && outline.unsupported == previous.unsupported | ||
| 629 | && (!new.title_outlines.contains(id) || outline.layout == previous.layout); | ||
| 630 | if !same { | ||
| 631 | return Err(invalid( | ||
| 632 | "Outline roles, indentation tables and title geometry cannot be edited", | ||
| 633 | )); | ||
| 634 | } | ||
| 635 | } | ||
| 636 | for (id, paragraph) in &new.paragraphs { | ||
| 637 | let Some(previous) = old.paragraphs.get(id) else { | ||
| 638 | continue; | ||
| 639 | }; | ||
| 640 | if paragraph.media != previous.media { | ||
| 641 | return Err(invalid("Recording annotations cannot be edited")); | ||
| 642 | } | ||
| 643 | let same = paragraph.style == previous.style && paragraph.format == previous.format; | ||
| 644 | if !same { | ||
| 645 | return Err(invalid( | ||
| 646 | "Paragraph styles and paragraph formatting cannot be edited", | ||
| 647 | )); | ||
| 648 | } | ||
| 649 | match (&paragraph.content, &previous.content) { | ||
| 650 | (ParagraphContent::Text(text), ParagraphContent::Text(previous)) => { | ||
| 651 | if text.date_field != previous.date_field { | ||
| 652 | return Err(invalid("Text fields cannot be edited")); | ||
| 653 | } | ||
| 654 | } | ||
| 655 | (ParagraphContent::Table(table), ParagraphContent::Table(previous)) => { | ||
| 656 | if table.id != previous.id | ||
| 657 | || table.layout != previous.layout | ||
| 658 | || table.tags != previous.tags | ||
| 659 | { | ||
| 660 | return Err(invalid("Table identity, layout and tags cannot be edited")); | ||
| 661 | } | ||
| 662 | let unchanged_cells = | ||
| 663 | table.rows.iter().flat_map(|row| &row.cells).all(|cell| { | ||
| 664 | previous | ||
| 665 | .rows | ||
| 666 | .iter() | ||
| 667 | .flat_map(|row| &row.cells) | ||
| 668 | .find(|before| before.id == cell.id) | ||
| 669 | .is_none_or(|before| { | ||
| 670 | cell.layout == before.layout | ||
| 671 | && cell.unsupported == before.unsupported | ||
| 672 | }) | ||
| 673 | }); | ||
| 674 | if !unchanged_cells { | ||
| 675 | return Err(invalid("Cell layout cannot be edited")); | ||
| 676 | } | ||
| 677 | } | ||
| 678 | (ParagraphContent::Unsupported(a), ParagraphContent::Unsupported(b)) if a == b => {} | ||
| 679 | (ParagraphContent::Ink(a), ParagraphContent::Ink(b)) => { | ||
| 680 | if a.id != b.id { | ||
| 681 | return Err(invalid("Ink identity cannot change")); | ||
| 682 | } | ||
| 683 | } | ||
| 684 | (ParagraphContent::Image(a), ParagraphContent::Image(b)) => { | ||
| 685 | picture_fixed_fields(a, b)?; | ||
| 686 | if (a.layout.x, a.layout.y) != (b.layout.x, b.layout.y) { | ||
| 687 | return Err(invalid("A paragraph picture has no position of its own")); | ||
| 688 | } | ||
| 689 | } | ||
| 690 | (ParagraphContent::Attachment(a), ParagraphContent::Attachment(b)) => { | ||
| 691 | if a.id != b.id { | ||
| 692 | return Err(invalid("Attachment identity cannot change")); | ||
| 693 | } | ||
| 694 | } | ||
| 695 | _ => return Err(invalid("Paragraph content type cannot change")), | ||
| 696 | } | ||
| 697 | } | ||
| 698 | Ok(()) | ||
| 699 | } | ||
| 700 | |||
| 701 | fn insert_outlines( | 184 | fn insert_outlines( |
| 702 | &mut self, | 185 | &mut self, |
| 703 | old: &View<'_>, | 186 | old: &View<'_>, |
| ... | @@ -758,6 +241,10 @@ impl<'a> Lowering<'a> { | ... | @@ -758,6 +241,10 @@ impl<'a> Lowering<'a> { |
| 758 | Ok(()) | 241 | Ok(()) |
| 759 | } | 242 | } |
| 760 | 243 | ||
| 244 | /// Splits and joins stored paragraphs as OneNote's Enter, Backspace and Delete do. A new | ||
| 245 | /// styled paragraph that is no exact split starts as Enter at the end of the nearest stored | ||
| 246 | /// paragraph that allows one, taking its properties as OneNote's new paragraphs do; later | ||
| 247 | /// passes place it and give it its text. | ||
| 761 | fn split_and_join( | 248 | fn split_and_join( |
| 762 | &mut self, | 249 | &mut self, |
| 763 | old: &View<'_>, | 250 | old: &View<'_>, |
| ... | @@ -765,65 +252,98 @@ impl<'a> Lowering<'a> { | ... | @@ -765,65 +252,98 @@ impl<'a> Lowering<'a> { |
| 765 | placed: &mut BTreeMap<ExGuid, Vec<ExGuid>>, | 252 | placed: &mut BTreeMap<ExGuid, Vec<ExGuid>>, |
| 766 | consumed: &mut BTreeSet<ExGuid>, | 253 | consumed: &mut BTreeSet<ExGuid>, |
| 767 | ) -> Result<(), Error> { | 254 | ) -> Result<(), Error> { |
| 768 | for (container, children) in &new.children { | 255 | // A new paragraph may be split from the stored paragraph nearest before it, whatever |
| 769 | if !old.children.contains_key(container) { | 256 | // placement or pasted paragraphs the same save adds between them. |
| 257 | let mut splits = Vec::new(); | ||
| 258 | for document in new.documents() { | ||
| 259 | let mut left = None; | ||
| 260 | for paragraph in document { | ||
| 261 | if old.paragraphs.contains_key(&paragraph.id) { | ||
| 262 | left = paragraph.text().is_some().then_some(paragraph.id); | ||
| 263 | } else if let Some(left) = left { | ||
| 264 | let container = new.container[&paragraph.id]; | ||
| 265 | let at = new.children[&container] | ||
| 266 | .iter() | ||
| 267 | .position(|id| *id == paragraph.id); | ||
| 268 | splits.push(((container, at), left, paragraph.id)); | ||
| 269 | } | ||
| 270 | } | ||
| 271 | } | ||
| 272 | splits.sort(); | ||
| 273 | let mut split = BTreeSet::new(); | ||
| 274 | for (_, left, right) in splits { | ||
| 275 | let (Some(previous), Some(after), Some(next)) = | ||
| 276 | (old.text(left), new.text(left), new.text(right)) | ||
| 277 | else { | ||
| 278 | continue; | ||
| 279 | }; | ||
| 280 | if split.contains(&left) | ||
| 281 | || after.id != previous.id | ||
| 282 | || new.paragraphs[&right].style != old.paragraphs[&left].style | ||
| 283 | { | ||
| 770 | continue; | 284 | continue; |
| 771 | } | 285 | } |
| 772 | for pair in children.windows(2) { | 286 | let Ok(offset) = after.text.utf16_offset(after.text.text().len()) else { |
| 773 | let [left, right] = [pair[0], pair[1]]; | 287 | continue; |
| 774 | let (Some(previous), Some(after), Some(next)) = | 288 | }; |
| 775 | (old.text(left), new.text(left), new.text(right)) | 289 | // Splitting at the end only differs from appending a paragraph by the copied |
| 776 | else { | 290 | // paragraph style; without one, an appended empty paragraph is an insertion. |
| 777 | continue; | 291 | if offset == previous.text.utf16_offset(previous.text.text().len())? |
| 778 | }; | 292 | && new.paragraphs[&right].style.is_none() |
| 779 | if old.paragraphs.contains_key(&right) | 293 | { |
| 780 | || old.container.get(&left) != Some(container) | 294 | continue; |
| 781 | || !new.children[&left].is_empty() | 295 | } |
| 782 | || new.children[&right] != old.children[&left] | 296 | let Ok(head) = previous.text.slice(0..offset) else { |
| 783 | || after.id != previous.id | 297 | continue; |
| 784 | || new.paragraphs[&right].style != old.paragraphs[&left].style | 298 | }; |
| 299 | let Ok(tail) = previous.text.slice( | ||
| 300 | offset | ||
| 301 | ..previous | ||
| 302 | .text | ||
| 303 | .utf16_offset(previous.text.text().len()) | ||
| 304 | .unwrap_or(0), | ||
| 305 | ) else { | ||
| 306 | continue; | ||
| 307 | }; | ||
| 308 | // An emptied side takes the writer's insertion style, so only its text must agree. | ||
| 309 | let same = |expected: &super::Paragraph, actual: &super::Paragraph| { | ||
| 310 | expected.text() == actual.text() | ||
| 311 | && (expected.text().is_empty() || expected == actual) | ||
| 312 | }; | ||
| 313 | if !same(&head, &after.text) || !same(&tail, &next.text) { | ||
| 314 | continue; | ||
| 315 | } | ||
| 316 | let edit = ParagraphSplit::new(previous.id, offset, self.author)?; | ||
| 317 | if self.split(&edit, left, right, old, new, placed)? { | ||
| 318 | split.insert(left); | ||
| 319 | } | ||
| 320 | } | ||
| 321 | for document in new.documents() { | ||
| 322 | for (at, paragraph) in document.iter().enumerate() { | ||
| 323 | if old.paragraphs.contains_key(&paragraph.id) | ||
| 324 | || self.alias.contains_key(&paragraph.id) | ||
| 325 | || paragraph.style.is_none() | ||
| 326 | || paragraph.text().is_none() | ||
| 785 | { | 327 | { |
| 786 | continue; | 328 | continue; |
| 787 | } | 329 | } |
| 788 | let Ok(offset) = after.text.utf16_offset(after.text.text().len()) else { | 330 | let stored = |candidate: &&&PageParagraph| { |
| 789 | continue; | 331 | old.paragraphs.contains_key(&candidate.id) && candidate.text().is_some() |
| 790 | }; | 332 | }; |
| 791 | // Splitting at the end only differs from appending a paragraph by the copied | 333 | let before = document[..at].iter().rev().filter(stored); |
| 792 | // paragraph style; without one, an appended empty paragraph is an insertion. | 334 | for template in before.chain(document[at + 1..].iter().filter(stored)) { |
| 793 | if offset == previous.text.utf16_offset(previous.text.text().len())? | 335 | let text = self.active.view.nodes[&template.id].content[0]; |
| 794 | && new.paragraphs[&right].style.is_none() | 336 | let Kind::RichText { text: content, .. } = &self.active.view.nodes[&text].kind |
| 795 | { | 337 | else { |
| 796 | continue; | 338 | continue; |
| 797 | } | 339 | }; |
| 798 | let Ok(head) = previous.text.slice(0..offset) else { | 340 | let end = u32::try_from(content.encode_utf16().count()) |
| 799 | continue; | 341 | .map_err(|_| invalid("Paragraph exceeds the UTF-16 offset range"))?; |
| 800 | }; | 342 | let edit = ParagraphSplit::new(text, end, self.author)?; |
| 801 | let Ok(tail) = previous.text.slice( | 343 | if self.split(&edit, template.id, paragraph.id, old, new, placed)? { |
| 802 | offset | 344 | break; |
| 803 | ..previous | 345 | } |
| 804 | .text | ||
| 805 | .utf16_offset(previous.text.text().len()) | ||
| 806 | .unwrap_or(0), | ||
| 807 | ) else { | ||
| 808 | continue; | ||
| 809 | }; | ||
| 810 | // An emptied side takes the writer's insertion style, so only its text must agree. | ||
| 811 | let same = |expected: &super::Paragraph, actual: &super::Paragraph| { | ||
| 812 | expected.text() == actual.text() | ||
| 813 | && (expected.text().is_empty() || expected == actual) | ||
| 814 | }; | ||
| 815 | if !same(&head, &after.text) || !same(&tail, &next.text) { | ||
| 816 | continue; | ||
| 817 | } | 346 | } |
| 818 | let split = ParagraphSplit::new(previous.id, offset, self.author)?; | ||
| 819 | self.write(|active| split.changes(active))?; | ||
| 820 | self.alias.insert(right, split.object()); | ||
| 821 | self.alias.insert(next.id, split.text_object()); | ||
| 822 | let list = placed.get_mut(container).unwrap(); | ||
| 823 | let at = list.iter().position(|id| *id == left).unwrap(); | ||
| 824 | list.insert(at + 1, right); | ||
| 825 | placed.insert(right, placed[&left].clone()); | ||
| 826 | placed.insert(left, Vec::new()); | ||
| 827 | } | 347 | } |
| 828 | } | 348 | } |
| 829 | for (container, children) in &old.children { | 349 | for (container, children) in &old.children { |
| ... | @@ -856,12 +376,13 @@ impl<'a> Lowering<'a> { | ... | @@ -856,12 +376,13 @@ impl<'a> Lowering<'a> { |
| 856 | previous.id | 376 | previous.id |
| 857 | } | 377 | } |
| 858 | { | 378 | { |
| 859 | return Err(invalid( | 379 | continue; |
| 860 | "A joined paragraph keeps the left text identity unless the left text was empty", | ||
| 861 | )); | ||
| 862 | } | 380 | } |
| 863 | let join = ParagraphJoin::new(previous.id, removed.id, self.author)?; | 381 | let join = ParagraphJoin::new(previous.id, removed.id, self.author)?; |
| 864 | self.write(|active| join.changes(active))?; | 382 | let Ok(changes) = join.changes(&self.active) else { |
| 383 | continue; | ||
| 384 | }; | ||
| 385 | self.write(|_| Ok(changes))?; | ||
| 865 | consumed.insert(right); | 386 | consumed.insert(right); |
| 866 | placed.get_mut(container).unwrap().retain(|id| *id != right); | 387 | placed.get_mut(container).unwrap().retain(|id| *id != right); |
| 867 | placed.remove(&right); | 388 | placed.remove(&right); |
| ... | @@ -870,6 +391,50 @@ impl<'a> Lowering<'a> { | ... | @@ -870,6 +391,50 @@ impl<'a> Lowering<'a> { |
| 870 | Ok(()) | 391 | Ok(()) |
| 871 | } | 392 | } |
| 872 | 393 | ||
| 394 | /// Stores `edit` of model paragraph `left` as model paragraph `right`, unless the split | ||
| 395 | /// cannot be made there. | ||
| 396 | fn split( | ||
| 397 | &mut self, | ||
| 398 | edit: &ParagraphSplit, | ||
| 399 | left: ExGuid, | ||
| 400 | right: ExGuid, | ||
| 401 | old: &View<'_>, | ||
| 402 | new: &View<'_>, | ||
| 403 | placed: &mut BTreeMap<ExGuid, Vec<ExGuid>>, | ||
| 404 | ) -> Result<bool, Error> { | ||
| 405 | let Ok(changes) = edit.changes(&self.active) else { | ||
| 406 | return Ok(false); | ||
| 407 | }; | ||
| 408 | self.write(|_| Ok(changes))?; | ||
| 409 | self.alias.insert(right, edit.object()); | ||
| 410 | // A text object a join moved into the paragraph is taken over after placement. | ||
| 411 | if let Some(text) = new.text(right) | ||
| 412 | && !self.active.live.revision.objects.contains_key(&text.id) | ||
| 413 | { | ||
| 414 | self.alias.insert(text.id, edit.text_object()); | ||
| 415 | } | ||
| 416 | // The split copies the left paragraph's list nodes in order; a list the model shares | ||
| 417 | // with a stored paragraph stays that paragraph's. | ||
| 418 | if new.paragraphs[&right].lists.len() == old.paragraphs[&left].lists.len() { | ||
| 419 | for (n, list) in (5..).zip(&new.paragraphs[&right].lists) { | ||
| 420 | if !self.active.live.revision.objects.contains_key(list) { | ||
| 421 | let guid = edit.object().guid; | ||
| 422 | self.alias.insert(*list, ExGuid { guid, n }); | ||
| 423 | } | ||
| 424 | } | ||
| 425 | } | ||
| 426 | // An earlier split may have carried the left paragraph to its tail. | ||
| 427 | let list = placed | ||
| 428 | .values_mut() | ||
| 429 | .find(|list| list.contains(&left)) | ||
| 430 | .unwrap(); | ||
| 431 | let at = list.iter().position(|id| *id == left).unwrap(); | ||
| 432 | list.insert(at + 1, right); | ||
| 433 | placed.insert(right, placed[&left].clone()); | ||
| 434 | placed.insert(left, Vec::new()); | ||
| 435 | Ok(true) | ||
| 436 | } | ||
| 437 | |||
| 873 | fn place( | 438 | fn place( |
| 874 | &mut self, | 439 | &mut self, |
| 875 | old: &View<'_>, | 440 | old: &View<'_>, |
| ... | @@ -957,9 +522,10 @@ impl<'a> Lowering<'a> { | ... | @@ -957,9 +522,10 @@ impl<'a> Lowering<'a> { |
| 957 | if pending.is_empty() { | 522 | if pending.is_empty() { |
| 958 | break; | 523 | break; |
| 959 | } | 524 | } |
| 960 | let (ready, waiting): (Vec<ExGuid>, Vec<ExGuid>) = pending | 525 | let (ready, waiting): (Vec<ExGuid>, Vec<ExGuid>) = |
| 961 | .into_iter() | 526 | pending.into_iter().partition(|container| { |
| 962 | .partition(|container| self.alias.contains_key(container)); | 527 | self.alias.contains_key(container) || old.children.contains_key(container) |
| 528 | }); | ||
| 963 | if ready.is_empty() { | 529 | if ready.is_empty() { |
| 964 | return Err(invalid("Table content has no table to hold it")); | 530 | return Err(invalid("Table content has no table to hold it")); |
| 965 | } | 531 | } |
| ... | @@ -981,18 +547,12 @@ impl<'a> Lowering<'a> { | ... | @@ -981,18 +547,12 @@ impl<'a> Lowering<'a> { |
| 981 | let ParagraphContent::Attachment(attachment) = &paragraph.content else { | 547 | let ParagraphContent::Attachment(attachment) = &paragraph.content else { |
| 982 | continue; | 548 | continue; |
| 983 | }; | 549 | }; |
| 984 | let name = attachment.filename.as_str(); | ||
| 985 | if name.is_empty() || name.contains(['\0', '/', '\\']) { | ||
| 986 | return Err(invalid( | ||
| 987 | "An attachment needs a file name without path separators", | ||
| 988 | )); | ||
| 989 | } | ||
| 990 | if let Some(previous) = old.paragraphs.get(paragraph_id) { | 550 | if let Some(previous) = old.paragraphs.get(paragraph_id) { |
| 991 | let ParagraphContent::Attachment(stored) = &previous.content else { | 551 | let ParagraphContent::Attachment(stored) = &previous.content else { |
| 992 | return Err(invalid("Paragraph content type cannot change")); | 552 | return Err(invalid("Paragraph content type cannot change")); |
| 993 | }; | 553 | }; |
| 994 | if stored != attachment { | 554 | if stored != attachment { |
| 995 | self.edit_attachment(stored, attachment)?; | 555 | self.write(|active| content::attachment_edit_changes(active, stored, attachment))?; |
| 996 | } | 556 | } |
| 997 | continue; | 557 | continue; |
| 998 | } | 558 | } |
| ... | @@ -1002,89 +562,32 @@ impl<'a> Lowering<'a> { | ... | @@ -1002,89 +562,32 @@ impl<'a> Lowering<'a> { |
| 1002 | let Some(bytes) = &attachment.bytes else { | 562 | let Some(bytes) = &attachment.bytes else { |
| 1003 | return Err(invalid("A new attachment needs its payload")); | 563 | return Err(invalid("A new attachment needs its payload")); |
| 1004 | }; | 564 | }; |
| 1005 | let extension = name | 565 | let ids = AttachmentIds { |
| 1006 | .rfind('.') | 566 | object: self.allocate(attachment.id)?, |
| 1007 | .filter(|dot| *dot > 0) | 567 | file: ExGuid { |
| 1008 | .map(|dot| &name[dot..]) | 568 | guid: crate::write::fresh_guid()?, |
| 1009 | .unwrap_or(""); | 569 | n: 1, |
| 1010 | let attachment_id = self.allocate(attachment.id)?; | 570 | }, |
| 1011 | let file_id = ExGuid { | 571 | payload: crate::write::fresh_guid()?, |
| 1012 | guid: crate::write::fresh_guid()?, | 572 | preview: match &attachment.preview { |
| 1013 | n: 1, | 573 | Some(_) => { |
| 574 | let payload = crate::write::fresh_guid()?; | ||
| 575 | let icon = ExGuid { | ||
| 576 | guid: crate::write::fresh_guid()?, | ||
| 577 | n: 1, | ||
| 578 | }; | ||
| 579 | Some((payload, icon)) | ||
| 580 | } | ||
| 581 | None => None, | ||
| 582 | }, | ||
| 1014 | }; | 583 | }; |
| 1015 | let payload_guid = crate::write::fresh_guid()?; | 584 | let mut payloads: Vec<([u8; 16], &[u8])> = vec![(ids.payload, bytes)]; |
| 1016 | let reference = payload_reference(payload_guid); | 585 | if let (Some((payload, _)), Some(icon)) = (ids.preview, &attachment.preview) { |
| 1017 | let modified = crate::create::current_timestamps()?.0.to_le_bytes(); | 586 | payloads.push((payload, icon)); |
| 1018 | let mut values: Values = vec![(0x14001d7a, modified.to_vec())]; | ||
| 1019 | if let Some([width, height]) = attachment.size { | ||
| 1020 | if !(width.is_finite() && height.is_finite() && width > 0.0 && height > 0.0) { | ||
| 1021 | return Err(invalid("Attachment icon size must be positive")); | ||
| 1022 | } | ||
| 1023 | values.push((0x140034cd, (width / 36.0).to_le_bytes().to_vec())); | ||
| 1024 | values.push((0x140034ce, (height / 36.0).to_le_bytes().to_vec())); | ||
| 1025 | } | ||
| 1026 | values.push((0x14001c3b, 0x409_u32.to_le_bytes().to_vec())); | ||
| 1027 | values.push((0x10001cfe, 0x409_u16.to_le_bytes().to_vec())); | ||
| 1028 | values.push((0x1c001dcf, vec![0; 32])); | ||
| 1029 | values.push(( | ||
| 1030 | 0x1c001d61, | ||
| 1031 | [16u32, 1, 0, 0, 0] | ||
| 1032 | .iter() | ||
| 1033 | .flat_map(|v| v.to_le_bytes()) | ||
| 1034 | .collect(), | ||
| 1035 | )); | ||
| 1036 | values.push((0x14001c3e, 1u32.to_le_bytes().to_vec())); | ||
| 1037 | values.push((0x14001c84, 1u32.to_le_bytes().to_vec())); | ||
| 1038 | values.push((0x1c001c22, crate::create::string(name))); | ||
| 1039 | values.push((0x1c001d9c, crate::create::string(name))); | ||
| 1040 | if let Some(path) = &attachment.source_path { | ||
| 1041 | values.push((0x1c001d9d, crate::create::string(path))); | ||
| 1042 | } | 587 | } |
| 1043 | let holder = self.id(*paragraph_id); | 588 | let holder = self.id(*paragraph_id); |
| 1044 | let mut payloads: Vec<([u8; 16], &[u8])> = vec![(payload_guid, bytes)]; | ||
| 1045 | let mut preview = None; | ||
| 1046 | if let Some(icon) = &attachment.preview { | ||
| 1047 | if !icon.starts_with(&[0x89, b'P', b'N', b'G']) { | ||
| 1048 | return Err(invalid("An attachment preview is a PNG icon")); | ||
| 1049 | } | ||
| 1050 | let guid = crate::write::fresh_guid()?; | ||
| 1051 | payloads.push((guid, icon)); | ||
| 1052 | preview = Some(( | ||
| 1053 | ExGuid { | ||
| 1054 | guid: crate::write::fresh_guid()?, | ||
| 1055 | n: 1, | ||
| 1056 | }, | ||
| 1057 | payload_reference(guid), | ||
| 1058 | )); | ||
| 1059 | } | ||
| 1060 | self.write_with(&payloads, |active| { | 589 | self.write_with(&payloads, |active| { |
| 1061 | let raw = &active.live.revision; | 590 | content::attachment_changes(active, attachment, &ids, holder) |
| 1062 | let mut changed = BTreeMap::new(); | ||
| 1063 | let mut file = PropertyObject::file(file_id, &reference, extension)?; | ||
| 1064 | file.jcid = 0x80036; | ||
| 1065 | changed.insert(file_id, file); | ||
| 1066 | let mut node = PropertyObject { | ||
| 1067 | jcid: 0x60035, | ||
| 1068 | bytes: crate::create::properties(&values)?, | ||
| 1069 | global_ids: std::sync::Arc::new(BTreeMap::from([(0, attachment_id.guid)])), | ||
| 1070 | }; | ||
| 1071 | node.reference(attachment_id)?; | ||
| 1072 | let container = node.reference(file_id)?; | ||
| 1073 | node.set(&[(0x20001d9b, &container)])?; | ||
| 1074 | if let Some((icon_id, icon_reference)) = &preview { | ||
| 1075 | changed.insert( | ||
| 1076 | *icon_id, | ||
| 1077 | PropertyObject::file(*icon_id, icon_reference, ".png")?, | ||
| 1078 | ); | ||
| 1079 | let icon = node.reference(*icon_id)?; | ||
| 1080 | node.set(&[(0x20001c3f, &icon)])?; | ||
| 1081 | } | ||
| 1082 | changed.insert(attachment_id, node); | ||
| 1083 | let mut object = PropertyObject::from_object(&raw.objects[&holder])?; | ||
| 1084 | let content = object.reference(attachment_id)?; | ||
| 1085 | object.set(&[(0x24001c1f, &content), (0x14001d7a, &modified)])?; | ||
| 1086 | changed.insert(holder, object); | ||
| 1087 | Ok(changed) | ||
| 1088 | })?; | 591 | })?; |
| 1089 | } | 592 | } |
| 1090 | Ok(()) | 593 | Ok(()) |
| ... | @@ -1183,15 +686,6 @@ impl<'a> Lowering<'a> { | ... | @@ -1183,15 +686,6 @@ impl<'a> Lowering<'a> { |
| 1183 | /// child (or a paragraph holds as content), its data node listing stroke objects, each | 686 | /// child (or a paragraph holds as content), its data node listing stroke objects, each |
| 1184 | /// stroke's packet and half-inch origin, and one drawing-attribute object per distinct pen. | 687 | /// stroke's packet and half-inch origin, and one drawing-attribute object per distinct pen. |
| 1185 | fn insert_ink(&mut self, ink: &Ink, holder: Option<ExGuid>) -> Result<(), Error> { | 688 | fn insert_ink(&mut self, ink: &Ink, holder: Option<ExGuid>) -> Result<(), Error> { |
| 1186 | if !ink.groups.is_empty() { | ||
| 1187 | return Err(invalid("New ink holds strokes, not nested groups")); | ||
| 1188 | } | ||
| 1189 | if ink.strokes.is_empty() { | ||
| 1190 | return Err(invalid("New ink needs at least one stroke")); | ||
| 1191 | } | ||
| 1192 | if ink.layout != Default::default() { | ||
| 1193 | return Err(invalid("Ink positions come from its strokes")); | ||
| 1194 | } | ||
| 1195 | let container = self.allocate(ink.id)?; | 689 | let container = self.allocate(ink.id)?; |
| 1196 | let data = ExGuid { | 690 | let data = ExGuid { |
| 1197 | guid: crate::write::fresh_guid()?, | 691 | guid: crate::write::fresh_guid()?, |
| ... | @@ -1201,50 +695,7 @@ impl<'a> Lowering<'a> { | ... | @@ -1201,50 +695,7 @@ impl<'a> Lowering<'a> { |
| 1201 | for stroke in &ink.strokes { | 695 | for stroke in &ink.strokes { |
| 1202 | strokes.push((self.allocate(stroke.id)?, stroke)); | 696 | strokes.push((self.allocate(stroke.id)?, stroke)); |
| 1203 | } | 697 | } |
| 1204 | let (modified, filetime) = crate::create::current_timestamps()?; | 698 | self.write(|active| content::ink_changes(active, ink, container, data, &strokes, holder)) |
| 1205 | let modified = modified.to_le_bytes(); | ||
| 1206 | let page = self.page; | ||
| 1207 | self.write(|active| { | ||
| 1208 | let raw = &active.live.revision; | ||
| 1209 | let mut changed = BTreeMap::new(); | ||
| 1210 | let mut data_object = PropertyObject { | ||
| 1211 | jcid: 0x2003b, | ||
| 1212 | bytes: crate::create::properties(&[])?, | ||
| 1213 | global_ids: std::sync::Arc::new(BTreeMap::from([(0, data.guid)])), | ||
| 1214 | }; | ||
| 1215 | data_object.reference(data)?; | ||
| 1216 | let references = write_strokes(&mut changed, &mut data_object, &strokes, 0, filetime)?; | ||
| 1217 | data_object.set(&[(0x24003416, &references)])?; | ||
| 1218 | changed.insert(data, data_object); | ||
| 1219 | let mut object = PropertyObject { | ||
| 1220 | jcid: 0x60014, | ||
| 1221 | bytes: crate::create::properties(&[ | ||
| 1222 | (0x14001d7a, modified.to_vec()), | ||
| 1223 | (0x14001d4e, 1u32.to_le_bytes().to_vec()), | ||
| 1224 | ])?, | ||
| 1225 | global_ids: std::sync::Arc::new(BTreeMap::from([(0, container.guid)])), | ||
| 1226 | }; | ||
| 1227 | object.reference(container)?; | ||
| 1228 | let data_reference = object.reference(data)?; | ||
| 1229 | object.set(&[(0x20003415, &data_reference)])?; | ||
| 1230 | changed.insert(container, object); | ||
| 1231 | match holder { | ||
| 1232 | Some(holder) => { | ||
| 1233 | let mut object = PropertyObject::from_object(&raw.objects[&holder])?; | ||
| 1234 | let content = object.reference(container)?; | ||
| 1235 | object.set(&[(0x24001c1f, &content), (0x14001d7a, &modified)])?; | ||
| 1236 | changed.insert(holder, object); | ||
| 1237 | } | ||
| 1238 | None => { | ||
| 1239 | let mut object = PropertyObject::from_object(&raw.objects[&page])?; | ||
| 1240 | let mut children = page_children(&object)?; | ||
| 1241 | children.extend(object.reference(container)?); | ||
| 1242 | object.set(&[(0x24001c20, &children), (0x14001d7a, &modified)])?; | ||
| 1243 | changed.insert(page, object); | ||
| 1244 | } | ||
| 1245 | } | ||
| 1246 | Ok(changed) | ||
| 1247 | }) | ||
| 1248 | } | 699 | } |
| 1249 | 700 | ||
| 1250 | /// Rewrites a drawing's stroke list: stored strokes stay as they are (OneNote erases whole | 701 | /// Rewrites a drawing's stroke list: stored strokes stay as they are (OneNote erases whole |
| ... | @@ -1266,204 +717,40 @@ impl<'a> Lowering<'a> { | ... | @@ -1266,204 +717,40 @@ impl<'a> Lowering<'a> { |
| 1266 | } | 717 | } |
| 1267 | } | 718 | } |
| 1268 | let container = self.id(ink.id); | 719 | let container = self.id(ink.id); |
| 1269 | let data = match self | 720 | self.write(|active| content::strokes_changes(active, container, &kept, &added)) |
| 1270 | .active | ||
| 1271 | .view | ||
| 1272 | .nodes | ||
| 1273 | .get(&container) | ||
| 1274 | .map(|node| &node.kind) | ||
| 1275 | { | ||
| 1276 | Some(crate::document::Kind::Ink { | ||
| 1277 | data: Some(data), .. | ||
| 1278 | }) => *data, | ||
| 1279 | _ => return Err(invalid("Stored ink has no stroke data to rewrite")), | ||
| 1280 | }; | ||
| 1281 | let (modified, filetime) = crate::create::current_timestamps()?; | ||
| 1282 | let modified = modified.to_le_bytes(); | ||
| 1283 | self.write(|active| { | ||
| 1284 | let raw = &active.live.revision; | ||
| 1285 | let mut changed = BTreeMap::new(); | ||
| 1286 | let mut data_object = PropertyObject::from_object(&raw.objects[&data])?; | ||
| 1287 | let mut references = Vec::new(); | ||
| 1288 | for id in &kept { | ||
| 1289 | references.extend(data_object.reference(*id)?); | ||
| 1290 | } | ||
| 1291 | references.extend(write_strokes( | ||
| 1292 | &mut changed, | ||
| 1293 | &mut data_object, | ||
| 1294 | &added, | ||
| 1295 | kept.len(), | ||
| 1296 | filetime, | ||
| 1297 | )?); | ||
| 1298 | data_object.set(&[(0x24003416, &references)])?; | ||
| 1299 | changed.insert(data, data_object); | ||
| 1300 | let mut object = PropertyObject::from_object(&raw.objects[&container])?; | ||
| 1301 | object.set(&[(0x14001d7a, &modified)])?; | ||
| 1302 | changed.insert(container, object); | ||
| 1303 | Ok(changed) | ||
| 1304 | }) | ||
| 1305 | } | ||
| 1306 | |||
| 1307 | /// Gives a new picture what OneNote stores for an inserted one: the payload embedded | ||
| 1308 | /// in the section's file-data store, a file-data object declaring it by identity and | ||
| 1309 | /// extension, and a picture object that a paragraph holds as content or the page | ||
| 1310 | /// lists as a child. | ||
| 1311 | fn insert_image(&mut self, image: &Image, holder: Option<ExGuid>) -> Result<(), Error> { | ||
| 1312 | let Some(bytes) = &image.bytes else { | ||
| 1313 | return Err(invalid("A new picture needs its payload")); | ||
| 1314 | }; | ||
| 1315 | let extension = match bytes.as_ref() { | ||
| 1316 | [0x89, b'P', b'N', b'G', ..] => ".png", | ||
| 1317 | [0xff, 0xd8, 0xff, ..] => ".jpg", | ||
| 1318 | [b'G', b'I', b'F', b'8', ..] => ".gif", | ||
| 1319 | [b'B', b'M', ..] => ".bmp", | ||
| 1320 | _ => return Err(invalid("Choose a PNG, JPEG, GIF or BMP picture")), | ||
| 1321 | }; | ||
| 1322 | let image_id = self.allocate(image.id)?; | ||
| 1323 | let file_id = ExGuid { | ||
| 1324 | guid: crate::write::fresh_guid()?, | ||
| 1325 | n: 1, | ||
| 1326 | }; | ||
| 1327 | let payload_guid = crate::write::fresh_guid()?; | ||
| 1328 | let reference = payload_reference(payload_guid); | ||
| 1329 | let modified = crate::create::current_timestamps()?.0.to_le_bytes(); | ||
| 1330 | let mut values: Values = vec![(0x14001d7a, modified.to_vec())]; | ||
| 1331 | if let Some([width, height]) = image.size { | ||
| 1332 | if !(width.is_finite() && height.is_finite() && width > 0.0 && height > 0.0) { | ||
| 1333 | return Err(invalid("Picture size must be positive")); | ||
| 1334 | } | ||
| 1335 | values.push((0x140034cd, (width / 36.0).to_le_bytes().to_vec())); | ||
| 1336 | values.push((0x140034ce, (height / 36.0).to_le_bytes().to_vec())); | ||
| 1337 | } | ||
| 1338 | values.extend(layout_values(&Default::default(), &image.layout)?.0); | ||
| 1339 | if let Some(alt) = &image.alt { | ||
| 1340 | values.push((0x1c001e58, crate::create::string(alt))); | ||
| 1341 | } | ||
| 1342 | if image.background { | ||
| 1343 | values.push((0x08001d13 | (1 << 31), Vec::new())); | ||
| 1344 | } | ||
| 1345 | values.push((0x08001d85, Vec::new())); | ||
| 1346 | let page = self.page; | ||
| 1347 | let payload: &[u8] = bytes; | ||
| 1348 | self.write_with(&[(payload_guid, payload)], |active| { | ||
| 1349 | let raw = &active.live.revision; | ||
| 1350 | let mut changed = BTreeMap::new(); | ||
| 1351 | let file = PropertyObject::file(file_id, &reference, extension)?; | ||
| 1352 | changed.insert(file_id, file); | ||
| 1353 | let mut picture = PropertyObject { | ||
| 1354 | jcid: 0x60011, | ||
| 1355 | bytes: crate::create::properties(&values)?, | ||
| 1356 | global_ids: std::sync::Arc::new(BTreeMap::from([(0, image_id.guid)])), | ||
| 1357 | }; | ||
| 1358 | picture.reference(image_id)?; | ||
| 1359 | let container = picture.reference(file_id)?; | ||
| 1360 | picture.set(&[(0x20001c3f, &container)])?; | ||
| 1361 | changed.insert(image_id, picture); | ||
| 1362 | match holder { | ||
| 1363 | Some(holder) => { | ||
| 1364 | let mut object = PropertyObject::from_object(&raw.objects[&holder])?; | ||
| 1365 | let content = object.reference(image_id)?; | ||
| 1366 | object.set(&[(0x24001c1f, &content), (0x14001d7a, &modified)])?; | ||
| 1367 | changed.insert(holder, object); | ||
| 1368 | } | ||
| 1369 | None => { | ||
| 1370 | let mut object = PropertyObject::from_object(&raw.objects[&page])?; | ||
| 1371 | let mut children = page_children(&object)?; | ||
| 1372 | children.extend(object.reference(image_id)?); | ||
| 1373 | object.set(&[(0x24001c20, &children), (0x14001d7a, &modified)])?; | ||
| 1374 | changed.insert(page, object); | ||
| 1375 | } | ||
| 1376 | } | ||
| 1377 | Ok(changed) | ||
| 1378 | }) | ||
| 1379 | } | 721 | } |
| 1380 | 722 | ||
| 1381 | /// Writes a moved, resized or described picture the way OneNote stores one: the | 723 | /// Gives a new picture what OneNote stores for an inserted one: the payload embedded |
| 1382 | /// position, the layout width and height with the user flag and the description on | 724 | /// in the section's file-data store, a file-data object declaring it by identity and |
| 1383 | /// the picture object, leaving the intrinsic size alone. | 725 | /// extension, and a picture object that a paragraph holds as content or the page |
| 1384 | /// A stored attachment keeps its payload and preview; its shown name, recorded source | 726 | /// lists as a child. |
| 1385 | /// path and icon size change in place, as OneNote's rename does. | 727 | fn insert_image(&mut self, image: &Image, holder: Option<ExGuid>) -> Result<(), Error> { |
| 1386 | fn edit_attachment( | 728 | let Some(bytes) = &image.bytes else { |
| 1387 | &mut self, | 729 | return Err(invalid("A new picture needs its payload")); |
| 1388 | stored: &Attachment, | 730 | }; |
| 1389 | attachment: &Attachment, | 731 | let image_id = self.allocate(image.id)?; |
| 1390 | ) -> Result<(), Error> { | 732 | let file_id = ExGuid { |
| 1391 | let mut values: Values = vec![( | 733 | guid: crate::write::fresh_guid()?, |
| 1392 | 0x14001d7a, | 734 | n: 1, |
| 1393 | crate::create::current_timestamps()? | 735 | }; |
| 1394 | .0 | 736 | let payload_guid = crate::write::fresh_guid()?; |
| 1395 | .to_le_bytes() | 737 | let payload: &[u8] = bytes; |
| 1396 | .to_vec(), | 738 | self.write_with(&[(payload_guid, payload)], |active| { |
| 1397 | )]; | 739 | content::picture_changes(active, image, image_id, file_id, payload_guid, holder) |
| 1398 | if attachment.recording != stored.recording { | ||
| 1399 | return Err(invalid("A recording stays the recording OneNote captured")); | ||
| 1400 | } | ||
| 1401 | let mut removed = Vec::new(); | ||
| 1402 | if attachment.filename != stored.filename { | ||
| 1403 | let name = crate::create::string(&attachment.filename); | ||
| 1404 | values.push((0x1c001c22, name.clone())); | ||
| 1405 | values.push((0x1c001d9c, name)); | ||
| 1406 | } | ||
| 1407 | if attachment.source_path != stored.source_path { | ||
| 1408 | match &attachment.source_path { | ||
| 1409 | Some(path) => values.push((0x1c001d9d, crate::create::string(path))), | ||
| 1410 | None => removed.push(0x1c001d9d), | ||
| 1411 | } | ||
| 1412 | } | ||
| 1413 | if attachment.size != stored.size { | ||
| 1414 | match attachment.size { | ||
| 1415 | Some([width, height]) => { | ||
| 1416 | if !(width.is_finite() && height.is_finite() && width > 0.0 && height > 0.0) { | ||
| 1417 | return Err(invalid("Attachment icon size must be positive")); | ||
| 1418 | } | ||
| 1419 | values.push((0x140034cd, (width / 36.0).to_le_bytes().to_vec())); | ||
| 1420 | values.push((0x140034ce, (height / 36.0).to_le_bytes().to_vec())); | ||
| 1421 | } | ||
| 1422 | None => removed.extend([0x140034cd, 0x140034ce]), | ||
| 1423 | } | ||
| 1424 | } | ||
| 1425 | let object = self.id(attachment.id); | ||
| 1426 | self.write(|active| { | ||
| 1427 | let raw = &active.live.revision; | ||
| 1428 | let mut node = PropertyObject::from_object(&raw.objects[&object])?; | ||
| 1429 | node.remove(&removed)?; | ||
| 1430 | let values: Vec<(u32, &[u8])> = values | ||
| 1431 | .iter() | ||
| 1432 | .map(|(id, bytes)| (*id, bytes.as_slice())) | ||
| 1433 | .collect(); | ||
| 1434 | node.set(&values)?; | ||
| 1435 | Ok(BTreeMap::from([(object, node)])) | ||
| 1436 | }) | 740 | }) |
| 1437 | } | 741 | } |
| 1438 | 742 | ||
| 1439 | fn edit_image(&mut self, stored: &Image, image: &Image) -> Result<(), Error> { | 743 | fn edit_image(&mut self, stored: &Image, image: &Image) -> Result<(), Error> { |
| 1440 | picture_fixed_fields(stored, image)?; | 744 | content::picture_fixed_fields(stored, image)?; |
| 1441 | let mut values: Values = vec![( | ||
| 1442 | 0x14001d7a, | ||
| 1443 | crate::create::current_timestamps()? | ||
| 1444 | .0 | ||
| 1445 | .to_le_bytes() | ||
| 1446 | .to_vec(), | ||
| 1447 | )]; | ||
| 1448 | let (layout, mut removed) = layout_values(&stored.layout, &image.layout)?; | ||
| 1449 | values.extend(layout); | ||
| 1450 | if image.alt != stored.alt { | ||
| 1451 | match &image.alt { | ||
| 1452 | Some(alt) => values.push((0x1c001e58, crate::create::string(alt))), | ||
| 1453 | None => removed.push(0x1c001e58), | ||
| 1454 | } | ||
| 1455 | } | ||
| 1456 | let object = self.id(image.id); | 745 | let object = self.id(image.id); |
| 1457 | self.write(|active| { | 746 | self.write(|active| { |
| 1458 | let raw = &active.live.revision; | 747 | content::picture_edit_changes( |
| 1459 | let mut picture = PropertyObject::from_object(&raw.objects[&object])?; | 748 | active, |
| 1460 | picture.remove(&removed)?; | 749 | object, |
| 1461 | let values: Vec<(u32, &[u8])> = values | 750 | (&stored.layout, &stored.alt), |
| 1462 | .iter() | 751 | &image.layout, |
| 1463 | .map(|(id, bytes)| (*id, bytes.as_slice())) | 752 | &image.alt, |
| 1464 | .collect(); | 753 | ) |
| 1465 | picture.set(&values)?; | ||
| 1466 | Ok(BTreeMap::from([(object, picture)])) | ||
| 1467 | }) | 754 | }) |
| 1468 | } | 755 | } |
| 1469 | 756 | ||
| ... | @@ -1528,7 +815,9 @@ impl<'a> Lowering<'a> { | ... | @@ -1528,7 +815,9 @@ impl<'a> Lowering<'a> { |
| 1528 | Insertion::paragraph(self.id(*container), anchor, text, self.author)?; | 815 | Insertion::paragraph(self.id(*container), anchor, text, self.author)?; |
| 1529 | self.write(|active| insertion.changes(active))?; | 816 | self.write(|active| insertion.changes(active))?; |
| 1530 | self.alias.insert(*id, insertion.object()); | 817 | self.alias.insert(*id, insertion.object()); |
| 1531 | if let Some(text_id) = text_id { | 818 | if let Some(text_id) = |
| 819 | text_id.filter(|id| !self.active.live.revision.objects.contains_key(id)) | ||
| 820 | { | ||
| 1532 | self.alias.insert(text_id, insertion.text_object()); | 821 | self.alias.insert(text_id, insertion.text_object()); |
| 1533 | } | 822 | } |
| 1534 | } else if !kept.contains(id) { | 823 | } else if !kept.contains(id) { |
| ... | @@ -1542,12 +831,7 @@ impl<'a> Lowering<'a> { | ... | @@ -1542,12 +831,7 @@ impl<'a> Lowering<'a> { |
| 1542 | Ok(()) | 831 | Ok(()) |
| 1543 | } | 832 | } |
| 1544 | 833 | ||
| 1545 | /// Creates tables, rows and cells the model added, rebuilds every table's row and cell | 834 | /// Changes the shading and indents of stored cells in place. |
| 1546 | /// order to the model's, and writes the column widths, locks and border flag. Native | ||
| 1547 | /// rows and cells are plain containers; a cell carries its indent array and the flags | ||
| 1548 | /// every native cell has. | ||
| 1549 | /// Shading (the documented `CellShadingColor`, which OneNote 2010 stores but neither | ||
| 1550 | /// renders nor accepts through its COM schema) and indents change in place on a cell. | ||
| 1551 | fn edit_cells(&mut self, old: &View<'_>, new: &View<'_>) -> Result<(), Error> { | 835 | fn edit_cells(&mut self, old: &View<'_>, new: &View<'_>) -> Result<(), Error> { |
| 1552 | let stored: BTreeMap<ExGuid, &super::TableCell> = old | 836 | let stored: BTreeMap<ExGuid, &super::TableCell> = old |
| 1553 | .paragraphs | 837 | .paragraphs |
| ... | @@ -1570,46 +854,25 @@ impl<'a> Lowering<'a> { | ... | @@ -1570,46 +854,25 @@ impl<'a> Lowering<'a> { |
| 1570 | if (cell.shading, &cell.indents) == (previous.shading, &previous.indents) { | 854 | if (cell.shading, &cell.indents) == (previous.shading, &previous.indents) { |
| 1571 | continue; | 855 | continue; |
| 1572 | } | 856 | } |
| 1573 | let mut values: Values = vec![( | ||
| 1574 | 0x14001d7a, | ||
| 1575 | crate::create::current_timestamps()? | ||
| 1576 | .0 | ||
| 1577 | .to_le_bytes() | ||
| 1578 | .to_vec(), | ||
| 1579 | )]; | ||
| 1580 | let mut removed = Vec::new(); | ||
| 1581 | if cell.shading != previous.shading { | ||
| 1582 | match cell.shading { | ||
| 1583 | Some(shading) => values.push((0x14001e26, shading.to_le_bytes().to_vec())), | ||
| 1584 | None => removed.push(0x14001e26), | ||
| 1585 | } | ||
| 1586 | } | ||
| 1587 | if cell.indents != previous.indents { | ||
| 1588 | if cell.indents.is_empty() { | ||
| 1589 | removed.push(0x1c001c12); | ||
| 1590 | } else { | ||
| 1591 | values.push((0x1c001c12, measurement_bytes(&cell.indents, 4)?)); | ||
| 1592 | } | ||
| 1593 | } | ||
| 1594 | let object = self.id(cell.id); | 857 | let object = self.id(cell.id); |
| 1595 | self.write(|active| { | 858 | self.write(|active| { |
| 1596 | let raw = &active.live.revision; | 859 | table::cell_changes( |
| 1597 | let mut object_properties = PropertyObject::from_object(&raw.objects[&object])?; | 860 | active, |
| 1598 | object_properties.remove(&removed)?; | 861 | object, |
| 1599 | let values: Vec<(u32, &[u8])> = values | 862 | (previous.shading, &previous.indents), |
| 1600 | .iter() | 863 | cell.shading, |
| 1601 | .map(|(id, bytes)| (*id, bytes.as_slice())) | 864 | &cell.indents, |
| 1602 | .collect(); | 865 | ) |
| 1603 | object_properties.set(&values)?; | ||
| 1604 | Ok(BTreeMap::from([(object, object_properties)])) | ||
| 1605 | })?; | 866 | })?; |
| 1606 | } | 867 | } |
| 1607 | } | 868 | } |
| 1608 | Ok(()) | 869 | Ok(()) |
| 1609 | } | 870 | } |
| 1610 | 871 | ||
| 872 | /// Creates tables, rows and cells the model added, rebuilds every table's row and cell | ||
| 873 | /// order to the model's, and writes the column widths, locks and border flag. | ||
| 1611 | fn edit_table_structure(&mut self, old: &View<'_>, new: &View<'_>) -> Result<(), Error> { | 874 | fn edit_table_structure(&mut self, old: &View<'_>, new: &View<'_>) -> Result<(), Error> { |
| 1612 | let old_tables: BTreeMap<ExGuid, &Table> = old | 875 | let old_tables: BTreeMap<ExGuid, &super::Table> = old |
| 1613 | .paragraphs | 876 | .paragraphs |
| 1614 | .values() | 877 | .values() |
| 1615 | .filter_map(|p| match &p.content { | 878 | .filter_map(|p| match &p.content { |
| ... | @@ -1642,8 +905,6 @@ impl<'a> Lowering<'a> { | ... | @@ -1642,8 +905,6 @@ impl<'a> Lowering<'a> { |
| 1642 | if unchanged { | 905 | if unchanged { |
| 1643 | continue; | 906 | continue; |
| 1644 | } | 907 | } |
| 1645 | let modified = crate::create::current_timestamps()?.0.to_le_bytes(); | ||
| 1646 | let mut created: Vec<(ExGuid, u32, Values)> = Vec::new(); | ||
| 1647 | let table_id = if previous.is_some() { | 908 | let table_id = if previous.is_some() { |
| 1648 | self.id(table.id) | 909 | self.id(table.id) |
| 1649 | } else { | 910 | } else { |
| ... | @@ -1662,13 +923,22 @@ impl<'a> Lowering<'a> { | ... | @@ -1662,13 +923,22 @@ impl<'a> Lowering<'a> { |
| 1662 | .unwrap_or_default(); | 923 | .unwrap_or_default(); |
| 1663 | let template = previous | 924 | let template = previous |
| 1664 | .and_then(|p| p.rows.first()) | 925 | .and_then(|p| p.rows.first()) |
| 1665 | .and_then(|r| r.cells.first()); | 926 | .and_then(|r| r.cells.first()) |
| 1666 | let mut rows = Vec::new(); | 927 | .map(|cell| cell.indents.as_slice()); |
| 928 | let mut structure = Structure { | ||
| 929 | rows: Vec::new(), | ||
| 930 | new_rows: BTreeSet::new(), | ||
| 931 | new_cells: BTreeMap::new(), | ||
| 932 | columns: &table.columns, | ||
| 933 | borders: table.borders, | ||
| 934 | }; | ||
| 1667 | for row in &table.rows { | 935 | for row in &table.rows { |
| 1668 | let row_id = if existing_rows.contains(&row.id) { | 936 | let row_id = if existing_rows.contains(&row.id) { |
| 1669 | self.id(row.id) | 937 | self.id(row.id) |
| 1670 | } else { | 938 | } else { |
| 1671 | self.allocate(row.id)? | 939 | let written = self.allocate(row.id)?; |
| 940 | structure.new_rows.insert(written); | ||
| 941 | written | ||
| 1672 | }; | 942 | }; |
| 1673 | let mut cells = Vec::new(); | 943 | let mut cells = Vec::new(); |
| 1674 | for cell in &row.cells { | 944 | for cell in &row.cells { |
| ... | @@ -1676,116 +946,18 @@ impl<'a> Lowering<'a> { | ... | @@ -1676,116 +946,18 @@ impl<'a> Lowering<'a> { |
| 1676 | self.id(cell.id) | 946 | self.id(cell.id) |
| 1677 | } else { | 947 | } else { |
| 1678 | let written = self.allocate(cell.id)?; | 948 | let written = self.allocate(cell.id)?; |
| 1679 | let indents = if cell.indents.is_empty() { | 949 | structure.new_cells.insert( |
| 1680 | template.map(|t| t.indents.clone()).unwrap_or_default() | 950 | written, |
| 1681 | } else { | 951 | (table::cell_indents(&cell.indents, template), cell.shading), |
| 1682 | cell.indents.clone() | 952 | ); |
| 1683 | }; | ||
| 1684 | let mut values: Values = vec![ | ||
| 1685 | (0x14001d7a, modified.to_vec()), | ||
| 1686 | (0x0c001c13, vec![0]), | ||
| 1687 | (0x0c001c03, vec![1]), | ||
| 1688 | (0x88001c91, Vec::new()), | ||
| 1689 | ]; | ||
| 1690 | if !indents.is_empty() { | ||
| 1691 | values.push((0x1c001c12, measurement_bytes(&indents, 4)?)); | ||
| 1692 | } | ||
| 1693 | if let Some(shading) = cell.shading { | ||
| 1694 | values.push((0x14001e26, shading.to_le_bytes().to_vec())); | ||
| 1695 | } | ||
| 1696 | created.push((written, 0x60024, values)); | ||
| 1697 | written | 953 | written |
| 1698 | }; | 954 | }; |
| 1699 | cells.push(cell_id); | 955 | cells.push(cell_id); |
| 1700 | } | 956 | } |
| 1701 | if !existing_rows.contains(&row.id) { | 957 | structure.rows.push((row_id, cells)); |
| 1702 | created.push((row_id, 0x60023, vec![(0x14001d7a, modified.to_vec())])); | ||
| 1703 | } | ||
| 1704 | rows.push((row_id, cells)); | ||
| 1705 | } | ||
| 1706 | let mut table_values: Values = vec![ | ||
| 1707 | (0x14001d7a, modified.to_vec()), | ||
| 1708 | (0x14001d57, (table.rows.len() as u32).to_le_bytes().to_vec()), | ||
| 1709 | ( | ||
| 1710 | 0x14001d58, | ||
| 1711 | (table.columns.len() as u32).to_le_bytes().to_vec(), | ||
| 1712 | ), | ||
| 1713 | ( | ||
| 1714 | 0x1c001d66, | ||
| 1715 | measurement_bytes( | ||
| 1716 | &table.columns.iter().map(|c| c.width).collect::<Vec<_>>(), | ||
| 1717 | 1, | ||
| 1718 | )?, | ||
| 1719 | ), | ||
| 1720 | ]; | ||
| 1721 | let mut locks = vec![table.columns.len() as u8]; | ||
| 1722 | locks.extend(vec![0u8; table.columns.len().div_ceil(8)]); | ||
| 1723 | for (i, column) in table.columns.iter().enumerate() { | ||
| 1724 | if column.locked { | ||
| 1725 | locks[1 + i / 8] |= 1 << (i % 8); | ||
| 1726 | } | ||
| 1727 | } | ||
| 1728 | table_values.push((0x1c001d7d, locks)); | ||
| 1729 | table_values.push(( | ||
| 1730 | 0x08001d5e | (u32::from(table.borders.unwrap_or(true)) << 31), | ||
| 1731 | Vec::new(), | ||
| 1732 | )); | ||
| 1733 | if previous.is_none() { | ||
| 1734 | table_values.push((0x14001c3e, 1u32.to_le_bytes().to_vec())); | ||
| 1735 | table_values.push((0x14001c84, 1u32.to_le_bytes().to_vec())); | ||
| 1736 | created.push((table_id, 0x60022, Vec::new())); | ||
| 1737 | } | 958 | } |
| 1738 | let holder = self.id(*paragraph_id); | 959 | let holder = previous.is_none().then(|| self.id(*paragraph_id)); |
| 1739 | let is_new = previous.is_none(); | 960 | self.write(|active| table::table_changes(active, table_id, holder, &structure))?; |
| 1740 | self.write(|active| { | ||
| 1741 | let raw = &active.live.revision; | ||
| 1742 | let mut changed: BTreeMap<ExGuid, PropertyObject> = BTreeMap::new(); | ||
| 1743 | for (id, jcid, values) in &created { | ||
| 1744 | let mut node = PropertyObject { | ||
| 1745 | jcid: *jcid, | ||
| 1746 | bytes: crate::create::properties(values)?, | ||
| 1747 | global_ids: std::sync::Arc::new(BTreeMap::from([(0, id.guid)])), | ||
| 1748 | }; | ||
| 1749 | node.reference(*id)?; | ||
| 1750 | changed.insert(*id, node); | ||
| 1751 | } | ||
| 1752 | let mut row_refs = Vec::new(); | ||
| 1753 | for (row_id, cells) in &rows { | ||
| 1754 | let mut row = match changed.remove(row_id) { | ||
| 1755 | Some(row) => row, | ||
| 1756 | None => PropertyObject::from_object(&raw.objects[row_id])?, | ||
| 1757 | }; | ||
| 1758 | let mut cell_refs = Vec::new(); | ||
| 1759 | for cell in cells { | ||
| 1760 | cell_refs.extend_from_slice(&row.reference(*cell)?); | ||
| 1761 | } | ||
| 1762 | row.set(&[(0x24001c20, &cell_refs), (0x14001d7a, &modified)])?; | ||
| 1763 | changed.insert(*row_id, row); | ||
| 1764 | } | ||
| 1765 | let mut table_object = match changed.remove(&table_id) { | ||
| 1766 | Some(object) => object, | ||
| 1767 | None => PropertyObject::from_object(&raw.objects[&table_id])?, | ||
| 1768 | }; | ||
| 1769 | for (row_id, _) in &rows { | ||
| 1770 | row_refs.extend_from_slice(&table_object.reference(*row_id)?); | ||
| 1771 | } | ||
| 1772 | table_object.remove(&[0x1c001d7d, 0x08001d5e])?; | ||
| 1773 | table_object.set( | ||
| 1774 | &table_values | ||
| 1775 | .iter() | ||
| 1776 | .map(|(id, bytes)| (*id, bytes.as_slice())) | ||
| 1777 | .collect::<Vec<_>>(), | ||
| 1778 | )?; | ||
| 1779 | table_object.set(&[(0x24001c20, &row_refs)])?; | ||
| 1780 | changed.insert(table_id, table_object); | ||
| 1781 | if is_new { | ||
| 1782 | let mut object = PropertyObject::from_object(&raw.objects[&holder])?; | ||
| 1783 | let reference = object.reference(table_id)?; | ||
| 1784 | object.set(&[(0x24001c1f, &reference), (0x14001d7a, &modified)])?; | ||
| 1785 | changed.insert(holder, object); | ||
| 1786 | } | ||
| 1787 | Ok(changed) | ||
| 1788 | })?; | ||
| 1789 | } | 961 | } |
| 1790 | Ok(()) | 962 | Ok(()) |
| 1791 | } | 963 | } |
| ... | @@ -1804,11 +976,10 @@ impl<'a> Lowering<'a> { | ... | @@ -1804,11 +976,10 @@ impl<'a> Lowering<'a> { |
| 1804 | } | 976 | } |
| 1805 | let mut ancestor = old.container[id]; | 977 | let mut ancestor = old.container[id]; |
| 1806 | let mut covered = false; | 978 | let mut covered = false; |
| 1807 | loop { | 979 | // A surviving ancestor is placed where the model says, carrying this paragraph along. |
| 980 | while !new.paragraphs.contains_key(&ancestor) { | ||
| 1808 | if removed_outline(ancestor) | 981 | if removed_outline(ancestor) |
| 1809 | || (old.paragraphs.contains_key(&ancestor) | 982 | || (old.paragraphs.contains_key(&ancestor) && !consumed.contains(&ancestor)) |
| 1810 | && !new.paragraphs.contains_key(&ancestor) | ||
| 1811 | && !consumed.contains(&ancestor)) | ||
| 1812 | || (old.children.contains_key(&ancestor) | 983 | || (old.children.contains_key(&ancestor) |
| 1813 | && !old.paragraphs.contains_key(&ancestor) | 984 | && !old.paragraphs.contains_key(&ancestor) |
| 1814 | && !old.outlines.contains_key(&ancestor) | 985 | && !old.outlines.contains_key(&ancestor) |
| ... | @@ -1853,120 +1024,116 @@ impl<'a> Lowering<'a> { | ... | @@ -1853,120 +1024,116 @@ impl<'a> Lowering<'a> { |
| 1853 | Ok(()) | 1024 | Ok(()) |
| 1854 | } | 1025 | } |
| 1855 | 1026 | ||
| 1856 | /// Rewrites every equation paragraph whose stored text object differs from the model, the | 1027 | /// Rewrites every equation paragraph whose stored text object differs from the model. |
| 1857 | /// way OneNote stores an equation: the linear text, one run per span with a style | ||
| 1858 | /// carrying the span's format, the run-data array naming each run's inline object, and | ||
| 1859 | /// the math language marker on the text object. | ||
| 1860 | fn edit_equations(&mut self, new: &View<'_>) -> Result<(), Error> { | 1028 | fn edit_equations(&mut self, new: &View<'_>) -> Result<(), Error> { |
| 1861 | let current = self.current()?; | 1029 | let current = self.current()?; |
| 1862 | let current = View::new(&current)?; | 1030 | let current = View::new(&current, false)?; |
| 1863 | for (id, paragraph) in &new.paragraphs { | 1031 | for (id, paragraph) in &new.paragraphs { |
| 1864 | let Some(text) = paragraph.text() else { | 1032 | let Some(text) = paragraph.text() else { |
| 1865 | continue; | 1033 | continue; |
| 1866 | }; | 1034 | }; |
| 1867 | if !Math::is_equation(&text.text) { | 1035 | let stored = current.text(self.id(*id)); |
| 1036 | // Deleting an equation leaves no math in the model, but its stored runs are still math. | ||
| 1037 | if !super::Math::is_equation(&text.text) | ||
| 1038 | && !stored.is_some_and(|stored| super::Math::is_equation(&stored.text)) | ||
| 1039 | { | ||
| 1868 | continue; | 1040 | continue; |
| 1869 | } | 1041 | } |
| 1870 | let stored = current | 1042 | let stored = stored |
| 1871 | .text(self.id(*id)) | ||
| 1872 | .ok_or_else(|| invalid("An equation paragraph is missing after placement"))?; | 1043 | .ok_or_else(|| invalid("An equation paragraph is missing after placement"))?; |
| 1873 | if stored.text == text.text { | 1044 | if stored.text == text.text { |
| 1874 | continue; | 1045 | continue; |
| 1875 | } | 1046 | } |
| 1876 | let object = stored.id; | 1047 | let object = stored.id; |
| 1877 | if text.text.text().contains('\u{fffc}') { | 1048 | self.write(|active| content::equation_changes(active, object, &text.text))?; |
| 1878 | return Err(invalid("Equations cannot hold embedded objects")); | 1049 | } |
| 1879 | } | 1050 | Ok(()) |
| 1880 | let encoded: Vec<u8> = text | 1051 | } |
| 1881 | .text | 1052 | |
| 1882 | .text() | 1053 | /// Lifts the paragraphs of outline groups into their container wherever this save changes |
| 1883 | .encode_utf16() | 1054 | /// the container's children or their depths, for placement to see them; `edit_levels` |
| 1884 | .chain([0]) | 1055 | /// groups them again. |
| 1885 | .flat_map(u16::to_le_bytes) | 1056 | fn ungroup(&mut self, old: &View<'_>, new: &View<'_>) -> Result<(), Error> { |
| 1886 | .collect(); | 1057 | for (container, children) in &old.children { |
| 1887 | let mut ends = Vec::new(); | 1058 | let Some(after) = new.children.get(container) else { |
| 1888 | let mut styles: Vec<Values> = Vec::new(); | 1059 | continue; |
| 1889 | let mut sets = Vec::new(); | 1060 | }; |
| 1890 | let all_math = text | 1061 | let grouped = self.active.view.nodes[container] |
| 1891 | .text | 1062 | .children |
| 1892 | .spans() | ||
| 1893 | .iter() | 1063 | .iter() |
| 1894 | .all(|s| s.format.math == Some(true)); | 1064 | .any(|id| matches!(self.active.view.nodes[id].kind, Kind::OutlineGroup)); |
| 1895 | for span in text.text.spans() { | 1065 | if !grouped |
| 1896 | ends.extend(text.text.utf16_offset(span.end)?.to_le_bytes()); | 1066 | || (after == children |
| 1897 | styles.push(style_values(&span.format)); | 1067 | && old.depths(*container, children) == new.depths(*container, after)) |
| 1898 | sets.push(match &span.format.math_object { | 1068 | { |
| 1899 | Some(object) => { | 1069 | continue; |
| 1900 | let mut set = vec![(0x1400344f, object.kind.to_le_bytes().to_vec())]; | 1070 | } |
| 1901 | if let Some(count) = object.arguments { | 1071 | let object = *container; |
| 1902 | set.push((0x14003450, count.to_le_bytes().to_vec())); | 1072 | self.write(|active| levels::ungroup_changes(active, object))?; |
| 1903 | } | 1073 | } |
| 1904 | if let Some(columns) = object.columns { | 1074 | Ok(()) |
| 1905 | set.push((0x0c003451, vec![columns])); | 1075 | } |
| 1906 | } | 1076 | |
| 1907 | for (id, symbol) in [0x10003453, 0x10003454, 0x10003455] | 1077 | /// Indents each container's children to the depths the model gives them. |
| 1908 | .into_iter() | 1078 | fn edit_levels(&mut self, new: &View<'_>) -> Result<(), Error> { |
| 1909 | .zip(&object.symbols) | 1079 | let current = self.current()?; |
| 1910 | { | 1080 | let stored = View::new(&current, false)?; |
| 1911 | let unit = u16::try_from(u32::from(*symbol)) | 1081 | for (container, children) in &new.children { |
| 1912 | .map_err(|_| invalid("Math symbols are single UTF-16 units"))?; | 1082 | let depths = new |
| 1913 | set.push((id, unit.to_le_bytes().to_vec())); | 1083 | .depths(*container, children) |
| 1914 | } | 1084 | .ok_or_else(|| invalid("A paragraph lies no deeper than its parent"))?; |
| 1915 | set | 1085 | let object = self.id(*container); |
| 1916 | } | 1086 | let written: Vec<ExGuid> = children.iter().map(|id| self.id(*id)).collect(); |
| 1917 | None => Vec::new(), | 1087 | if children.is_empty() || stored.depths(object, &written) == Some(depths.clone()) { |
| 1918 | }); | 1088 | continue; |
| 1089 | } | ||
| 1090 | let is_cell = | ||
| 1091 | !new.paragraphs.contains_key(container) && !new.outlines.contains_key(container); | ||
| 1092 | let (level, runs) = levels::runs(&written, &depths, is_cell)?; | ||
| 1093 | self.write(|active| levels::regroup_changes(active, object, level, &runs))?; | ||
| 1094 | } | ||
| 1095 | Ok(()) | ||
| 1096 | } | ||
| 1097 | |||
| 1098 | /// Gives a paragraph the text object its model names when a split and a join in one save | ||
| 1099 | /// moved text between paragraphs: a stored object is taken over, as OneNote moves a lower | ||
| 1100 | /// paragraph's text into an emptied upper one; a new one starts as a copy of the text | ||
| 1101 | /// the paragraph holds. | ||
| 1102 | fn edit_text_identities(&mut self, new: &View<'_>) -> Result<(), Error> { | ||
| 1103 | for (id, paragraph) in &new.paragraphs { | ||
| 1104 | let Some(text) = paragraph.text() else { | ||
| 1105 | continue; | ||
| 1106 | }; | ||
| 1107 | let object = self.id(*id); | ||
| 1108 | let stored = self.active.view.nodes[&object].content[0]; | ||
| 1109 | if stored == self.id(text.id) { | ||
| 1110 | continue; | ||
| 1919 | } | 1111 | } |
| 1920 | ends.truncate(ends.len() - 4); | 1112 | let target = if self.active.live.revision.objects.contains_key(&text.id) { |
| 1921 | let has_objects = sets.iter().any(|set| !set.is_empty()); | 1113 | text.id |
| 1922 | let modified = crate::create::current_timestamps()?.0.to_le_bytes(); | 1114 | } else { |
| 1115 | self.allocate(text.id)? | ||
| 1116 | }; | ||
| 1117 | let unstyled = paragraph.style.is_none(); | ||
| 1923 | self.write(|active| { | 1118 | self.write(|active| { |
| 1119 | let parents = active.editable_parents(object)?; | ||
| 1120 | let modified = crate::create::current_timestamps()?.0.to_le_bytes(); | ||
| 1924 | let raw = &active.live.revision; | 1121 | let raw = &active.live.revision; |
| 1925 | let mut changed = BTreeMap::new(); | 1122 | let mut changed = BTreeMap::new(); |
| 1926 | let mut target = PropertyObject::from_object(&raw.objects[&object])?; | 1123 | if !raw.objects.contains_key(&target) { |
| 1927 | let mut references = Vec::new(); | 1124 | let mut copy = PropertyObject::from_object(&raw.objects[&stored])?; |
| 1928 | let mut created: Vec<(Values, ExGuid)> = Vec::new(); | 1125 | copy.remove(&[0x1c001c98, 0x14001c99])?; |
| 1929 | for values in &styles { | 1126 | if unstyled { |
| 1930 | let id = match created.iter().find(|(known, _)| known == values) { | 1127 | copy.remove(&[0x2000342c])?; |
| 1931 | Some((_, id)) => *id, | 1128 | } |
| 1932 | None => { | 1129 | copy.reference(target)?; |
| 1933 | let id = ExGuid { | 1130 | changed.insert(target, copy); |
| 1934 | guid: crate::write::fresh_guid()?, | ||
| 1935 | n: 1, | ||
| 1936 | }; | ||
| 1937 | let mut style = PropertyObject { | ||
| 1938 | jcid: 0x12004d, | ||
| 1939 | bytes: crate::create::properties(values)?, | ||
| 1940 | global_ids: std::sync::Arc::new(BTreeMap::from([(0, id.guid)])), | ||
| 1941 | }; | ||
| 1942 | style.reference(id)?; | ||
| 1943 | changed.insert(id, style); | ||
| 1944 | created.push((values.clone(), id)); | ||
| 1945 | id | ||
| 1946 | } | ||
| 1947 | }; | ||
| 1948 | references.extend(target.reference(id)?); | ||
| 1949 | } | ||
| 1950 | target.remove(&[0x1c003498, 0x40003499])?; | ||
| 1951 | target.set(&[ | ||
| 1952 | (0x1c001c22, &encoded), | ||
| 1953 | (0x1c001e12, &ends), | ||
| 1954 | (0x24001e13, &references), | ||
| 1955 | (0x14001d7a, &modified), | ||
| 1956 | ])?; | ||
| 1957 | if has_objects { | ||
| 1958 | target.set_sets(0x40003499, 0x44000811, &sets)?; | ||
| 1959 | } | ||
| 1960 | if all_math { | ||
| 1961 | // The flags and language marker OneNote's equation editor leaves on | ||
| 1962 | // every equation text object. | ||
| 1963 | target.set(&[ | ||
| 1964 | (0x10001cfe, &0x7f_u16.to_le_bytes()), | ||
| 1965 | (0x14001c3e, &1u32.to_le_bytes()), | ||
| 1966 | (0x14001c84, &1u32.to_le_bytes()), | ||
| 1967 | ])?; | ||
| 1968 | } | 1131 | } |
| 1969 | changed.insert(object, target); | 1132 | let mut holder = PropertyObject::from_object(&raw.objects[&object])?; |
| 1133 | let reference = holder.reference(target)?; | ||
| 1134 | holder.set(&[(0x24001c1f, &reference), (0x14001d7a, &modified)])?; | ||
| 1135 | changed.insert(object, holder); | ||
| 1136 | crate::formatting::touch_ancestors(raw, parents, object, &modified, &mut changed)?; | ||
| 1970 | Ok(changed) | 1137 | Ok(changed) |
| 1971 | })?; | 1138 | })?; |
| 1972 | } | 1139 | } |
| ... | @@ -1975,7 +1142,7 @@ impl<'a> Lowering<'a> { | ... | @@ -1975,7 +1142,7 @@ impl<'a> Lowering<'a> { |
| 1975 | 1142 | ||
| 1976 | fn edit_text(&mut self, new: &View<'_>) -> Result<(), Error> { | 1143 | fn edit_text(&mut self, new: &View<'_>) -> Result<(), Error> { |
| 1977 | let current = self.current()?; | 1144 | let current = self.current()?; |
| 1978 | let current = View::new(&current)?; | 1145 | let current = View::new(&current, false)?; |
| 1979 | for (id, paragraph) in &new.paragraphs { | 1146 | for (id, paragraph) in &new.paragraphs { |
| 1980 | let Some(text) = paragraph.text() else { | 1147 | let Some(text) = paragraph.text() else { |
| 1981 | continue; | 1148 | continue; |
| ... | @@ -2003,7 +1170,7 @@ impl<'a> Lowering<'a> { | ... | @@ -2003,7 +1170,7 @@ impl<'a> Lowering<'a> { |
| 2003 | /// nodes belong to one paragraph, and a dropped reference leaves the node unreferenced. | 1170 | /// nodes belong to one paragraph, and a dropped reference leaves the node unreferenced. |
| 2004 | fn edit_lists(&mut self, after: &Page, new: &View<'_>) -> Result<(), Error> { | 1171 | fn edit_lists(&mut self, after: &Page, new: &View<'_>) -> Result<(), Error> { |
| 2005 | let current = self.current()?; | 1172 | let current = self.current()?; |
| 2006 | let stored = View::new(&current)?; | 1173 | let stored = View::new(&current, false)?; |
| 2007 | let mut owners: BTreeMap<ExGuid, ExGuid> = BTreeMap::new(); | 1174 | let mut owners: BTreeMap<ExGuid, ExGuid> = BTreeMap::new(); |
| 2008 | for (id, paragraph) in &stored.paragraphs { | 1175 | for (id, paragraph) in &stored.paragraphs { |
| 2009 | for list in &paragraph.lists { | 1176 | for list in &paragraph.lists { |
| ... | @@ -2022,7 +1189,8 @@ impl<'a> Lowering<'a> { | ... | @@ -2022,7 +1189,8 @@ impl<'a> Lowering<'a> { |
| 2022 | .iter() | 1189 | .iter() |
| 2023 | .zip(&previous.lists) | 1190 | .zip(&previous.lists) |
| 2024 | .all(|(model, node)| { | 1191 | .all(|(model, node)| { |
| 2025 | after.definitions.get(model) == current.definitions.get(node) | 1192 | self.id(*model) == *node |
| 1193 | && after.definitions.get(model) == current.definitions.get(node) | ||
| 2026 | }); | 1194 | }); |
| 2027 | if same { | 1195 | if same { |
| 2028 | continue; | 1196 | continue; |
| ... | @@ -2032,12 +1200,14 @@ impl<'a> Lowering<'a> { | ... | @@ -2032,12 +1200,14 @@ impl<'a> Lowering<'a> { |
| 2032 | // Writer-allocated identities keep a nonzero sequence number so the node's | 1200 | // Writer-allocated identities keep a nonzero sequence number so the node's |
| 2033 | // compact identity is never the null identity; squash renames them to the | 1201 | // compact identity is never the null identity; squash renames them to the |
| 2034 | // model's. A copy for a second owner keeps its allocated identity. | 1202 | // model's. A copy for a second owner keeps its allocated identity. |
| 2035 | let node_id = match owners.get(list) { | 1203 | let node_id = match owners.get(&self.id(*list)) { |
| 2036 | Some(owner) if *owner == image_id => *list, | 1204 | Some(owner) if *owner == image_id => self.id(*list), |
| 2037 | Some(_) => ExGuid { | 1205 | Some(_) => ExGuid { |
| 2038 | guid: crate::write::fresh_guid()?, | 1206 | guid: crate::write::fresh_guid()?, |
| 2039 | n: 1, | 1207 | n: 1, |
| 2040 | }, | 1208 | }, |
| 1209 | // A node its paragraph let go of in this save serves the model again. | ||
| 1210 | None if self.active.live.revision.objects.contains_key(list) => *list, | ||
| 2041 | None => { | 1211 | None => { |
| 2042 | let written = ExGuid { | 1212 | let written = ExGuid { |
| 2043 | guid: crate::write::fresh_guid()?, | 1213 | guid: crate::write::fresh_guid()?, |
| ... | @@ -2051,113 +1221,10 @@ impl<'a> Lowering<'a> { | ... | @@ -2051,113 +1221,10 @@ impl<'a> Lowering<'a> { |
| 2051 | .definitions | 1221 | .definitions |
| 2052 | .get(list) | 1222 | .get(list) |
| 2053 | .ok_or_else(|| invalid("A paragraph references a missing list definition"))?; | 1223 | .ok_or_else(|| invalid("A paragraph references a missing list definition"))?; |
| 2054 | let Kind::List { | 1224 | nodes.push((node_id, properties::list_values(definition)?)); |
| 2055 | font, | ||
| 2056 | format, | ||
| 2057 | restart, | ||
| 2058 | bullet, | ||
| 2059 | } = &definition.kind | ||
| 2060 | else { | ||
| 2061 | return Err(invalid("A paragraph list must reference a list definition")); | ||
| 2062 | }; | ||
| 2063 | if bullet.is_some() && (format.is_none() || font.is_none()) { | ||
| 2064 | return Err(invalid("A bullet definition names its glyph and font")); | ||
| 2065 | } | ||
| 2066 | let mut values: Values = Vec::new(); | ||
| 2067 | if let Some(format) = format { | ||
| 2068 | let units: Vec<u16> = format.encode_utf16().collect(); | ||
| 2069 | let count = u16::try_from(units.len()) | ||
| 2070 | .map_err(|_| invalid("List format exceeds the document range"))?; | ||
| 2071 | let mut bytes = count.to_le_bytes().to_vec(); | ||
| 2072 | bytes.extend(units.iter().flat_map(|unit| unit.to_le_bytes())); | ||
| 2073 | values.push((0x1c001c1a, bytes)); | ||
| 2074 | } | ||
| 2075 | if let Some(font) = font { | ||
| 2076 | values.push((0x1c001c52, crate::create::string(font))); | ||
| 2077 | } | ||
| 2078 | if let Some(restart) = restart { | ||
| 2079 | values.push((0x14001cb7, restart.to_le_bytes().to_vec())); | ||
| 2080 | } | ||
| 2081 | if let Some(bullet) = bullet { | ||
| 2082 | values.push((0x10001d0e, bullet.to_le_bytes().to_vec())); | ||
| 2083 | // Every native bullet node carries this cleared flag alongside its index. | ||
| 2084 | values.push((0x0c001cc0, vec![0])); | ||
| 2085 | } | ||
| 2086 | let style = &definition.format; | ||
| 2087 | if let Some(font) = &style.font { | ||
| 2088 | values.push((0x1c001c0a, crate::create::string(font))); | ||
| 2089 | } | ||
| 2090 | if let Some(size) = style.font_size { | ||
| 2091 | let half = (size * 2.0).round(); | ||
| 2092 | if !(0.0..=f32::from(u16::MAX)).contains(&half) { | ||
| 2093 | return Err(invalid("List font size is outside the document range")); | ||
| 2094 | } | ||
| 2095 | values.push((0x10001c0b, (half as u16).to_le_bytes().to_vec())); | ||
| 2096 | } | ||
| 2097 | if let Some(color) = style.color { | ||
| 2098 | values.push((0x14001c0c, color.to_le_bytes().to_vec())); | ||
| 2099 | } | ||
| 2100 | if let Some(language) = style.language { | ||
| 2101 | values.push((0x14001c3b, language.to_le_bytes().to_vec())); | ||
| 2102 | } | ||
| 2103 | for (flag, id) in [(style.bold, 0x08001c04), (style.italic, 0x08001c05)] { | ||
| 2104 | if let Some(flag) = flag { | ||
| 2105 | values.push((id | (u32::from(flag) << 31), Vec::new())); | ||
| 2106 | } | ||
| 2107 | } | ||
| 2108 | nodes.push((node_id, values)); | ||
| 2109 | } | 1225 | } |
| 2110 | let object = self.id(*id); | 1226 | let object = self.id(*id); |
| 2111 | self.write(|active| { | 1227 | self.write(|active| properties::list_changes(active, object, &nodes))?; |
| 2112 | let parents = active.editable_parents(object)?; | ||
| 2113 | let modified = crate::create::current_timestamps()?.0.to_le_bytes(); | ||
| 2114 | let raw = &active.live.revision; | ||
| 2115 | let mut changed = BTreeMap::new(); | ||
| 2116 | let mut target = PropertyObject::from_object(&raw.objects[&object])?; | ||
| 2117 | let mut references = Vec::new(); | ||
| 2118 | for (list, values) in &nodes { | ||
| 2119 | let mut node = match raw.objects.get(list) { | ||
| 2120 | Some(existing) => { | ||
| 2121 | if existing.jcid != 0x60012 { | ||
| 2122 | return Err(invalid( | ||
| 2123 | "A list definition identity belongs to another object", | ||
| 2124 | )); | ||
| 2125 | } | ||
| 2126 | let mut node = PropertyObject::from_object(existing)?; | ||
| 2127 | node.remove(&[ | ||
| 2128 | 0x1c001c1a, 0x1c001c52, 0x14001cb7, 0x10001d0e, 0x0c001cc0, | ||
| 2129 | 0x1c001c0a, 0x10001c0b, 0x14001c0c, 0x14001c3b, 0x08001c04, | ||
| 2130 | 0x08001c05, | ||
| 2131 | ])?; | ||
| 2132 | node | ||
| 2133 | } | ||
| 2134 | None => PropertyObject { | ||
| 2135 | jcid: 0x60012, | ||
| 2136 | bytes: crate::create::properties(&[])?, | ||
| 2137 | global_ids: std::sync::Arc::new(BTreeMap::from([(0, list.guid)])), | ||
| 2138 | }, | ||
| 2139 | }; | ||
| 2140 | node.set( | ||
| 2141 | &values | ||
| 2142 | .iter() | ||
| 2143 | .map(|(id, bytes)| (*id, bytes.as_slice())) | ||
| 2144 | .collect::<Vec<_>>(), | ||
| 2145 | )?; | ||
| 2146 | node.set(&[(0x14001d7a, &modified)])?; | ||
| 2147 | node.reference(*list)?; | ||
| 2148 | references.extend_from_slice(&target.reference(*list)?); | ||
| 2149 | changed.insert(*list, node); | ||
| 2150 | } | ||
| 2151 | if references.is_empty() { | ||
| 2152 | target.remove(&[0x24001c26])?; | ||
| 2153 | } else { | ||
| 2154 | target.set(&[(0x24001c26, &references)])?; | ||
| 2155 | } | ||
| 2156 | target.set(&[(0x14001d7a, &modified)])?; | ||
| 2157 | changed.insert(object, target); | ||
| 2158 | crate::formatting::touch_ancestors(raw, parents, object, &modified, &mut changed)?; | ||
| 2159 | Ok(changed) | ||
| 2160 | })?; | ||
| 2161 | } | 1228 | } |
| 2162 | Ok(()) | 1229 | Ok(()) |
| 2163 | } | 1230 | } |
| ... | @@ -2166,23 +1233,8 @@ impl<'a> Lowering<'a> { | ... | @@ -2166,23 +1233,8 @@ impl<'a> Lowering<'a> { |
| 2166 | /// the stored ones. A tag definition new to the section becomes a definition object | 1233 | /// the stored ones. A tag definition new to the section becomes a definition object |
| 2167 | /// carrying the model's identity after squash. | 1234 | /// carrying the model's identity after squash. |
| 2168 | fn edit_tags(&mut self, after: &Page, new: &View<'_>) -> Result<(), Error> { | 1235 | fn edit_tags(&mut self, after: &Page, new: &View<'_>) -> Result<(), Error> { |
| 2169 | fn same(a: &[Tag], b: &[Tag]) -> bool { | ||
| 2170 | let key = |t: &Tag| { | ||
| 2171 | ( | ||
| 2172 | t.definition, | ||
| 2173 | t.action_type, | ||
| 2174 | t.status, | ||
| 2175 | t.created, | ||
| 2176 | t.completed, | ||
| 2177 | t.start, | ||
| 2178 | t.due, | ||
| 2179 | t.task_id, | ||
| 2180 | ) | ||
| 2181 | }; | ||
| 2182 | a.len() == b.len() && a.iter().zip(b).all(|(x, y)| key(x) == key(y)) | ||
| 2183 | } | ||
| 2184 | let current = self.current()?; | 1236 | let current = self.current()?; |
| 2185 | let stored = View::new(&current)?; | 1237 | let stored = View::new(&current, false)?; |
| 2186 | for (id, paragraph) in &new.paragraphs { | 1238 | for (id, paragraph) in &new.paragraphs { |
| 2187 | let image_id = self.id(*id); | 1239 | let image_id = self.id(*id); |
| 2188 | let previous = stored | 1240 | let previous = stored |
| ... | @@ -2194,28 +1246,15 @@ impl<'a> Lowering<'a> { | ... | @@ -2194,28 +1246,15 @@ impl<'a> Lowering<'a> { |
| 2194 | targets.push((self.id(text.id), &text.tags, &before.tags)); | 1246 | targets.push((self.id(text.id), &text.tags, &before.tags)); |
| 2195 | } | 1247 | } |
| 2196 | for (object, tags, stored_tags) in targets { | 1248 | for (object, tags, stored_tags) in targets { |
| 2197 | if same(tags, stored_tags) { | 1249 | if crate::op::lower::same_tags(tags, stored_tags) { |
| 2198 | continue; | 1250 | continue; |
| 2199 | } | 1251 | } |
| 2200 | let mut definitions: Vec<(ExGuid, Option<Values>)> = Vec::new(); | 1252 | let mut entries = Vec::new(); |
| 2201 | let mut sets: Vec<(usize, Values)> = Vec::new(); | ||
| 2202 | let mut action_types = BTreeSet::new(); | ||
| 2203 | for tag in tags { | 1253 | for tag in tags { |
| 2204 | let definition = tag | 1254 | let definition = tag |
| 2205 | .definition | 1255 | .definition |
| 2206 | .ok_or_else(|| invalid("A note tag names its definition"))?; | 1256 | .ok_or_else(|| invalid("A note tag names its definition"))?; |
| 2207 | let action_type = if tag.status & 4 != 0 { | 1257 | let known = self.active.live.revision.objects.contains_key(&definition) |
| 2208 | tag.action_type | ||
| 2209 | } else { | ||
| 2210 | match after.definitions.get(&definition).map(|d| &d.kind) { | ||
| 2211 | Some(Kind::TagDefinition { action_type, .. }) => *action_type, | ||
| 2212 | _ => return Err(invalid("A note tag must reference a tag definition")), | ||
| 2213 | } | ||
| 2214 | }; | ||
| 2215 | if !action_types.insert(action_type.unwrap_or(0)) { | ||
| 2216 | return Err(invalid("An element holds one note tag per action type")); | ||
| 2217 | } | ||
| 2218 | let known = current.definitions.contains_key(&definition) | ||
| 2219 | || self.alias.contains_key(&definition); | 1258 | || self.alias.contains_key(&definition); |
| 2220 | let written = if known { | 1259 | let written = if known { |
| 2221 | self.id(definition) | 1260 | self.id(definition) |
| ... | @@ -2227,107 +1266,9 @@ impl<'a> Lowering<'a> { | ... | @@ -2227,107 +1266,9 @@ impl<'a> Lowering<'a> { |
| 2227 | self.alias.insert(definition, allocated); | 1266 | self.alias.insert(definition, allocated); |
| 2228 | allocated | 1267 | allocated |
| 2229 | }; | 1268 | }; |
| 2230 | let index = match definitions.iter().position(|(id, _)| *id == written) { | 1269 | entries.push((written, tag, after.definitions.get(&definition))); |
| 2231 | Some(index) => index, | ||
| 2232 | None => { | ||
| 2233 | let values = if known { | ||
| 2234 | None | ||
| 2235 | } else { | ||
| 2236 | let Some(model) = after.definitions.get(&definition) else { | ||
| 2237 | return Err(invalid( | ||
| 2238 | "A note tag references a missing tag definition", | ||
| 2239 | )); | ||
| 2240 | }; | ||
| 2241 | let Kind::TagDefinition { | ||
| 2242 | label, | ||
| 2243 | action_type, | ||
| 2244 | shape, | ||
| 2245 | color, | ||
| 2246 | highlight, | ||
| 2247 | } = &model.kind | ||
| 2248 | else { | ||
| 2249 | return Err(invalid( | ||
| 2250 | "A note tag must reference a tag definition", | ||
| 2251 | )); | ||
| 2252 | }; | ||
| 2253 | let mut values: Values = vec![ | ||
| 2254 | (0x0c003473, vec![0]), | ||
| 2255 | (0x10003463, action_type.unwrap_or(0).to_le_bytes().to_vec()), | ||
| 2256 | (0x10003464, shape.unwrap_or(0).to_le_bytes().to_vec()), | ||
| 2257 | (0x14003467, 0u32.to_le_bytes().to_vec()), | ||
| 2258 | ]; | ||
| 2259 | if let Some(label) = label { | ||
| 2260 | values.push((0x1c003468, crate::create::string(label))); | ||
| 2261 | } | ||
| 2262 | if let Some(color) = color { | ||
| 2263 | values.push((0x14003466, color.to_le_bytes().to_vec())); | ||
| 2264 | } | ||
| 2265 | if let Some(highlight) = highlight { | ||
| 2266 | values.push((0x14003465, highlight.to_le_bytes().to_vec())); | ||
| 2267 | } | ||
| 2268 | Some(values) | ||
| 2269 | }; | ||
| 2270 | definitions.push((written, values)); | ||
| 2271 | definitions.len() - 1 | ||
| 2272 | } | ||
| 2273 | }; | ||
| 2274 | let mut fields: Values = Vec::new(); | ||
| 2275 | if let Some(action_type) = tag.action_type { | ||
| 2276 | fields.push((0x10003463, action_type.to_le_bytes().to_vec())); | ||
| 2277 | } | ||
| 2278 | for (id, value) in [ | ||
| 2279 | (0x1400346e, tag.created), | ||
| 2280 | (0x1400346f, tag.completed), | ||
| 2281 | (0x1400346a, tag.start), | ||
| 2282 | (0x1400346b, tag.due), | ||
| 2283 | ] { | ||
| 2284 | if let Some(value) = value { | ||
| 2285 | fields.push((id, value.to_le_bytes().to_vec())); | ||
| 2286 | } | ||
| 2287 | } | ||
| 2288 | fields.push((0x10003470, tag.status.to_le_bytes().to_vec())); | ||
| 2289 | if let Some(task) = tag.task_id { | ||
| 2290 | fields.push((0x1c003469, task.to_vec())); | ||
| 2291 | } | ||
| 2292 | sets.push((index, fields)); | ||
| 2293 | } | 1270 | } |
| 2294 | self.write(|active| { | 1271 | self.write(|active| properties::tag_changes(active, object, &entries))?; |
| 2295 | let parents = active.editable_parents(object)?; | ||
| 2296 | let modified = crate::create::current_timestamps()?.0.to_le_bytes(); | ||
| 2297 | let raw = &active.live.revision; | ||
| 2298 | let mut changed = BTreeMap::new(); | ||
| 2299 | for (id, values) in &definitions { | ||
| 2300 | let Some(values) = values else { | ||
| 2301 | continue; | ||
| 2302 | }; | ||
| 2303 | let mut node = PropertyObject { | ||
| 2304 | jcid: 0x120043, | ||
| 2305 | bytes: crate::create::properties(values)?, | ||
| 2306 | global_ids: std::sync::Arc::new(BTreeMap::from([(0, id.guid)])), | ||
| 2307 | }; | ||
| 2308 | node.reference(*id)?; | ||
| 2309 | changed.insert(*id, node); | ||
| 2310 | } | ||
| 2311 | let mut target = PropertyObject::from_object(&raw.objects[&object])?; | ||
| 2312 | let mut encoded = Vec::new(); | ||
| 2313 | for (index, fields) in &sets { | ||
| 2314 | let reference = target.reference(definitions[*index].0)?; | ||
| 2315 | let mut set = vec![(0x20003488, reference.to_vec())]; | ||
| 2316 | set.extend(fields.iter().cloned()); | ||
| 2317 | encoded.push(set); | ||
| 2318 | } | ||
| 2319 | target.set_sets(0x40003489, 0x44000811, &encoded)?; | ||
| 2320 | target.set(&[(0x14001d7a, &modified)])?; | ||
| 2321 | changed.insert(object, target); | ||
| 2322 | crate::formatting::touch_ancestors( | ||
| 2323 | raw, | ||
| 2324 | parents, | ||
| 2325 | object, | ||
| 2326 | &modified, | ||
| 2327 | &mut changed, | ||
| 2328 | )?; | ||
| 2329 | Ok(changed) | ||
| 2330 | })?; | ||
| 2331 | } | 1272 | } |
| 2332 | } | 1273 | } |
| 2333 | Ok(()) | 1274 | Ok(()) |
| ... | @@ -2342,7 +1283,6 @@ impl<'a> Lowering<'a> { | ... | @@ -2342,7 +1283,6 @@ impl<'a> Lowering<'a> { |
| 2342 | new: &View<'_>, | 1283 | new: &View<'_>, |
| 2343 | after: &Page, | 1284 | after: &Page, |
| 2344 | ) -> Result<(), Error> { | 1285 | ) -> Result<(), Error> { |
| 2345 | let current = self.current()?; | ||
| 2346 | for (id, paragraph) in &new.paragraphs { | 1286 | for (id, paragraph) in &new.paragraphs { |
| 2347 | let Some(definition) = paragraph.style else { | 1287 | let Some(definition) = paragraph.style else { |
| 2348 | continue; | 1288 | continue; |
| ... | @@ -2357,7 +1297,7 @@ impl<'a> Lowering<'a> { | ... | @@ -2357,7 +1297,7 @@ impl<'a> Lowering<'a> { |
| 2357 | let Some(text) = paragraph.text() else { | 1297 | let Some(text) = paragraph.text() else { |
| 2358 | return Err(invalid("Only text paragraphs take a paragraph style")); | 1298 | return Err(invalid("Only text paragraphs take a paragraph style")); |
| 2359 | }; | 1299 | }; |
| 2360 | let known = current.definitions.contains_key(&definition) | 1300 | let known = self.active.live.revision.objects.contains_key(&definition) |
| 2361 | || self.alias.contains_key(&definition); | 1301 | || self.alias.contains_key(&definition); |
| 2362 | let style_id = if known { | 1302 | let style_id = if known { |
| 2363 | self.id(definition) | 1303 | self.id(definition) |
| ... | @@ -2369,59 +1309,16 @@ impl<'a> Lowering<'a> { | ... | @@ -2369,59 +1309,16 @@ impl<'a> Lowering<'a> { |
| 2369 | self.alias.insert(definition, allocated); | 1309 | self.alias.insert(definition, allocated); |
| 2370 | allocated | 1310 | allocated |
| 2371 | }; | 1311 | }; |
| 2372 | let values = if known { | ||
| 2373 | None | ||
| 2374 | } else { | ||
| 2375 | let Some(model) = after.definitions.get(&definition) else { | ||
| 2376 | return Err(invalid("A paragraph references a missing style definition")); | ||
| 2377 | }; | ||
| 2378 | let Kind::Style { name } = &model.kind else { | ||
| 2379 | return Err(invalid("A paragraph style must be a style definition")); | ||
| 2380 | }; | ||
| 2381 | let mut values = style_values(&model.format); | ||
| 2382 | if let Some(name) = name { | ||
| 2383 | values.push((0x1c00345a, crate::create::string(name))); | ||
| 2384 | } | ||
| 2385 | if let Some(alignment) = model.format.alignment { | ||
| 2386 | values.push((0x0c003477, vec![alignment])); | ||
| 2387 | } | ||
| 2388 | for (property, value) in [ | ||
| 2389 | (0x1400342e, model.format.space_before), | ||
| 2390 | (0x1400342f, model.format.space_after), | ||
| 2391 | (0x14003430, model.format.line_spacing), | ||
| 2392 | ] { | ||
| 2393 | if let Some(points) = value { | ||
| 2394 | values.push((property, (points / 36.0).to_le_bytes().to_vec())); | ||
| 2395 | } | ||
| 2396 | } | ||
| 2397 | Some(values) | ||
| 2398 | }; | ||
| 2399 | let object = self.id(text.id); | 1312 | let object = self.id(text.id); |
| 2400 | self.write(|active| { | 1313 | let definition = after.definitions.get(&definition); |
| 2401 | let raw = &active.live.revision; | 1314 | self.write(|active| properties::style_changes(active, object, style_id, definition))?; |
| 2402 | let mut changed = BTreeMap::new(); | ||
| 2403 | if let Some(values) = &values { | ||
| 2404 | let mut node = PropertyObject { | ||
| 2405 | jcid: 0x12004d, | ||
| 2406 | bytes: crate::create::properties(values)?, | ||
| 2407 | global_ids: std::sync::Arc::new(BTreeMap::from([(0, style_id.guid)])), | ||
| 2408 | }; | ||
| 2409 | node.reference(style_id)?; | ||
| 2410 | changed.insert(style_id, node); | ||
| 2411 | } | ||
| 2412 | let mut target = PropertyObject::from_object(&raw.objects[&object])?; | ||
| 2413 | let reference = target.reference(style_id)?; | ||
| 2414 | target.set(&[(0x2000342c, &reference)])?; | ||
| 2415 | changed.insert(object, target); | ||
| 2416 | Ok(changed) | ||
| 2417 | })?; | ||
| 2418 | } | 1315 | } |
| 2419 | Ok(()) | 1316 | Ok(()) |
| 2420 | } | 1317 | } |
| 2421 | 1318 | ||
| 2422 | fn edit_paragraph_formatting(&mut self, new: &View<'_>) -> Result<(), Error> { | 1319 | fn edit_paragraph_formatting(&mut self, new: &View<'_>) -> Result<(), Error> { |
| 2423 | let current = self.current()?; | 1320 | let current = self.current()?; |
| 2424 | let current = View::new(&current)?; | 1321 | let current = View::new(&current, false)?; |
| 2425 | for (id, paragraph) in &new.paragraphs { | 1322 | for (id, paragraph) in &new.paragraphs { |
| 2426 | let Some(text) = paragraph.text() else { | 1323 | let Some(text) = paragraph.text() else { |
| 2427 | continue; | 1324 | continue; |
| ... | @@ -2429,51 +1326,7 @@ impl<'a> Lowering<'a> { | ... | @@ -2429,51 +1326,7 @@ impl<'a> Lowering<'a> { |
| 2429 | let stored = current | 1326 | let stored = current |
| 2430 | .text(self.id(*id)) | 1327 | .text(self.id(*id)) |
| 2431 | .ok_or_else(|| invalid("A paragraph is missing after text edits"))?; | 1328 | .ok_or_else(|| invalid("A paragraph is missing after text edits"))?; |
| 2432 | let mut values: Vec<(u32, Vec<u8>)> = Vec::new(); | 1329 | let values = crate::op::lower::paragraph_change(&stored.text, &text.text)?; |
| 2433 | macro_rules! field { | ||
| 2434 | ($field:ident, $value:ident, $encode:block) => { | ||
| 2435 | let $value = format_in(&text.text, 0)?.$field.unwrap_or_default(); | ||
| 2436 | if text | ||
| 2437 | .text | ||
| 2438 | .spans() | ||
| 2439 | .iter() | ||
| 2440 | .all(|span| span.format.$field.unwrap_or_default() == $value) | ||
| 2441 | && stored | ||
| 2442 | .text | ||
| 2443 | .spans() | ||
| 2444 | .iter() | ||
| 2445 | .any(|span| span.format.$field.unwrap_or_default() != $value) | ||
| 2446 | { | ||
| 2447 | values.push($encode); | ||
| 2448 | } | ||
| 2449 | }; | ||
| 2450 | } | ||
| 2451 | field!(alignment, value, { | ||
| 2452 | if value > 2 { | ||
| 2453 | return Err(invalid("Paragraph alignment must be left, center or right")); | ||
| 2454 | } | ||
| 2455 | (0x0c003477, vec![value]) | ||
| 2456 | }); | ||
| 2457 | field!(rtl, value, { | ||
| 2458 | (0x08003476 | (u32::from(value) << 31), Vec::new()) | ||
| 2459 | }); | ||
| 2460 | macro_rules! spacing { | ||
| 2461 | ($field:ident, $property:expr) => { | ||
| 2462 | field!($field, value, { | ||
| 2463 | let stored = value / 36.0; | ||
| 2464 | if !stored.is_finite() || !(0.0..=27777.777).contains(&stored) { | ||
| 2465 | return Err(invalid("Paragraph spacing is outside the document range")); | ||
| 2466 | } | ||
| 2467 | ($property, stored.to_le_bytes().to_vec()) | ||
| 2468 | }); | ||
| 2469 | }; | ||
| 2470 | } | ||
| 2471 | spacing!(space_before, 0x1400342e); | ||
| 2472 | spacing!(space_after, 0x1400342f); | ||
| 2473 | spacing!(line_spacing, 0x14003430); | ||
| 2474 | field!(language, value, { | ||
| 2475 | (0x14001c3b, value.to_le_bytes().to_vec()) | ||
| 2476 | }); | ||
| 2477 | if values.is_empty() { | 1330 | if values.is_empty() { |
| 2478 | continue; | 1331 | continue; |
| 2479 | } | 1332 | } |
| ... | @@ -2483,48 +1336,14 @@ impl<'a> Lowering<'a> { | ... | @@ -2483,48 +1336,14 @@ impl<'a> Lowering<'a> { |
| 2483 | )); | 1336 | )); |
| 2484 | } | 1337 | } |
| 2485 | let object = stored.id; | 1338 | let object = stored.id; |
| 2486 | self.write(|active| { | 1339 | self.write(|active| properties::paragraph_format_changes(active, object, &values))?; |
| 2487 | let parents = active.editable_parents(object)?; | ||
| 2488 | let modified = crate::create::current_timestamps()?.0.to_le_bytes(); | ||
| 2489 | let raw = &active.live.revision; | ||
| 2490 | let mut target = PropertyObject::from_object(&raw.objects[&object])?; | ||
| 2491 | target.set( | ||
| 2492 | &values | ||
| 2493 | .iter() | ||
| 2494 | .map(|(id, bytes)| (*id, bytes.as_slice())) | ||
| 2495 | .collect::<Vec<_>>(), | ||
| 2496 | )?; | ||
| 2497 | if let Some((_, alignment)) = values.iter().find(|(id, _)| *id == 0x0c003477) { | ||
| 2498 | for property in [0x14001c3e, 0x14001c84] { | ||
| 2499 | let fields = PropertySets::parse(&target.bytes)?; | ||
| 2500 | let previous = fields.sets[0] | ||
| 2501 | .iter() | ||
| 2502 | .find(|field| field.id == property) | ||
| 2503 | .map(|field| match field.value { | ||
| 2504 | Value::Bytes(bytes) => bytes | ||
| 2505 | .try_into() | ||
| 2506 | .map(u32::from_le_bytes) | ||
| 2507 | .map_err(|_| invalid("Invalid paragraph layout alignment")), | ||
| 2508 | _ => Err(invalid("Invalid paragraph layout alignment")), | ||
| 2509 | }) | ||
| 2510 | .transpose()? | ||
| 2511 | .unwrap_or(0); | ||
| 2512 | let value = (previous & !7) | (u32::from(alignment[0]) + 1); | ||
| 2513 | target.set(&[(property, &value.to_le_bytes())])?; | ||
| 2514 | } | ||
| 2515 | } | ||
| 2516 | target.set(&[(0x14001d7a, &modified)])?; | ||
| 2517 | let mut changed = BTreeMap::from([(object, target)]); | ||
| 2518 | crate::formatting::touch_ancestors(raw, parents, object, &modified, &mut changed)?; | ||
| 2519 | Ok(changed) | ||
| 2520 | })?; | ||
| 2521 | } | 1340 | } |
| 2522 | Ok(()) | 1341 | Ok(()) |
| 2523 | } | 1342 | } |
| 2524 | 1343 | ||
| 2525 | fn edit_formatting(&mut self, new: &View<'_>) -> Result<(), Error> { | 1344 | fn edit_formatting(&mut self, new: &View<'_>) -> Result<(), Error> { |
| 2526 | let current = self.current()?; | 1345 | let current = self.current()?; |
| 2527 | let current = View::new(&current)?; | 1346 | let current = View::new(&current, false)?; |
| 2528 | for (id, paragraph) in &new.paragraphs { | 1347 | for (id, paragraph) in &new.paragraphs { |
| 2529 | let Some(text) = paragraph.text() else { | 1348 | let Some(text) = paragraph.text() else { |
| 2530 | continue; | 1349 | continue; |
| ... | @@ -2536,56 +1355,10 @@ impl<'a> Lowering<'a> { | ... | @@ -2536,56 +1355,10 @@ impl<'a> Lowering<'a> { |
| 2536 | return Err(invalid("Text edits did not converge on the model")); | 1355 | return Err(invalid("Text edits did not converge on the model")); |
| 2537 | } | 1356 | } |
| 2538 | let fresh = self.alias.contains_key(&text.id); | 1357 | let fresh = self.alias.contains_key(&text.id); |
| 2539 | let mut boundaries = BTreeSet::new(); | 1358 | for (range, set, clear) in crate::op::lower::format_edits(&stored.text, &text.text, fresh)? { |
| 2540 | for paragraph in [&stored.text, &text.text] { | ||
| 2541 | for span in paragraph.spans() { | ||
| 2542 | boundaries.insert(paragraph.utf16_offset(span.end)?); | ||
| 2543 | } | ||
| 2544 | } | ||
| 2545 | boundaries.insert(0); | ||
| 2546 | let boundaries: Vec<u32> = boundaries.into_iter().collect(); | ||
| 2547 | let mut pending: Option<(Range<u32>, Vec<TextAttribute>)> = None; | ||
| 2548 | let mut edits = Vec::new(); | ||
| 2549 | for window in boundaries.windows(2) { | ||
| 2550 | let (start, end) = (window[0], window[1]); | ||
| 2551 | if start == end { | ||
| 2552 | continue; | ||
| 2553 | } | ||
| 2554 | let attributes = attributes( | ||
| 2555 | format_in(&stored.text, start)?, | ||
| 2556 | format_in(&text.text, start)?, | ||
| 2557 | fresh, | ||
| 2558 | )?; | ||
| 2559 | match &mut pending { | ||
| 2560 | Some((range, previous)) if *previous == attributes && range.end == start => { | ||
| 2561 | range.end = end; | ||
| 2562 | } | ||
| 2563 | _ => { | ||
| 2564 | if let Some(edit) = pending.take() { | ||
| 2565 | edits.push(edit); | ||
| 2566 | } | ||
| 2567 | pending = Some((start..end, attributes)); | ||
| 2568 | } | ||
| 2569 | } | ||
| 2570 | } | ||
| 2571 | edits.extend(pending); | ||
| 2572 | if text.text.text().is_empty() { | ||
| 2573 | let attributes = attributes( | ||
| 2574 | format_in(&stored.text, 0)?, | ||
| 2575 | format_in(&text.text, 0)?, | ||
| 2576 | fresh, | ||
| 2577 | )?; | ||
| 2578 | if !attributes.is_empty() { | ||
| 2579 | edits.push((0..0, attributes)); | ||
| 2580 | } | ||
| 2581 | } | ||
| 2582 | for (range, attributes) in edits { | ||
| 2583 | if attributes.is_empty() { | ||
| 2584 | continue; | ||
| 2585 | } | ||
| 2586 | let object = stored.id; | 1359 | let object = stored.id; |
| 2587 | self.write(|active| { | 1360 | self.write(|active| { |
| 2588 | crate::formatting::format_changes(active, object, range, &attributes) | 1361 | crate::formatting::format_changes(active, object, range, &set, &clear) |
| 2589 | })?; | 1362 | })?; |
| 2590 | } | 1363 | } |
| 2591 | } | 1364 | } |
| ... | @@ -2594,7 +1367,7 @@ impl<'a> Lowering<'a> { | ... | @@ -2594,7 +1367,7 @@ impl<'a> Lowering<'a> { |
| 2594 | 1367 | ||
| 2595 | fn edit_layout(&mut self, old: &View<'_>, new: &View<'_>) -> Result<(), Error> { | 1368 | fn edit_layout(&mut self, old: &View<'_>, new: &View<'_>) -> Result<(), Error> { |
| 2596 | let current = self.current()?; | 1369 | let current = self.current()?; |
| 2597 | let current = View::new(&current)?; | 1370 | let current = View::new(&current, false)?; |
| 2598 | for (id, paragraph) in &new.paragraphs { | 1371 | for (id, paragraph) in &new.paragraphs { |
| 2599 | let stored = current | 1372 | let stored = current |
| 2600 | .paragraphs | 1373 | .paragraphs |
| ... | @@ -2621,7 +1394,7 @@ impl<'a> Lowering<'a> { | ... | @@ -2621,7 +1394,7 @@ impl<'a> Lowering<'a> { |
| 2621 | && !outline.indents.is_empty() | 1394 | && !outline.indents.is_empty() |
| 2622 | && outline.indents != stored.indents | 1395 | && outline.indents != stored.indents |
| 2623 | { | 1396 | { |
| 2624 | let indents = measurement_bytes(&outline.indents, 4)?; | 1397 | let indents = content::measurement_bytes(&outline.indents, 4)?; |
| 2625 | self.write(|active| { | 1398 | self.write(|active| { |
| 2626 | let raw = &active.live.revision; | 1399 | let raw = &active.live.revision; |
| 2627 | let mut node = PropertyObject::from_object(&raw.objects[&object])?; | 1400 | let mut node = PropertyObject::from_object(&raw.objects[&object])?; |
| ... | @@ -2654,217 +1427,6 @@ impl<'a> Lowering<'a> { | ... | @@ -2654,217 +1427,6 @@ impl<'a> Lowering<'a> { |
| 2654 | } | 1427 | } |
| 2655 | } | 1428 | } |
| 2656 | 1429 | ||
| 2657 | fn collect_containers(list: &[PageParagraph], out: &mut Vec<ExGuid>) { | ||
| 2658 | for paragraph in list { | ||
| 2659 | out.push(paragraph.id); | ||
| 2660 | if let ParagraphContent::Table(table) = &paragraph.content { | ||
| 2661 | for row in &table.rows { | ||
| 2662 | for cell in &row.cells { | ||
| 2663 | out.push(cell.id); | ||
| 2664 | collect_containers(&cell.paragraphs, out); | ||
| 2665 | } | ||
| 2666 | } | ||
| 2667 | } | ||
| 2668 | } | ||
| 2669 | } | ||
| 2670 | |||
| 2671 | /// Identities that keep their stored position: a longest increasing run of survivors, | ||
| 2672 | /// always including immovable ones. `after` may contain identities absent from `before`. | ||
| 2673 | fn kept_set( | ||
| 2674 | before: &[ExGuid], | ||
| 2675 | after: &[ExGuid], | ||
| 2676 | movable: impl Fn(ExGuid) -> bool, | ||
| 2677 | ) -> Result<BTreeSet<ExGuid>, Error> { | ||
| 2678 | let position: BTreeMap<ExGuid, usize> = | ||
| 2679 | after.iter().enumerate().map(|(i, id)| (*id, i)).collect(); | ||
| 2680 | let sequence: Vec<(ExGuid, usize)> = before | ||
| 2681 | .iter() | ||
| 2682 | .filter_map(|id| position.get(id).map(|at| (*id, *at))) | ||
| 2683 | .collect(); | ||
| 2684 | let fixed: Vec<usize> = sequence | ||
| 2685 | .iter() | ||
| 2686 | .filter(|(id, _)| !movable(*id)) | ||
| 2687 | .map(|(_, at)| *at) | ||
| 2688 | .collect(); | ||
| 2689 | if fixed.windows(2).any(|w| w[0] > w[1]) { | ||
| 2690 | return Err(invalid( | ||
| 2691 | "Images and unsupported objects cannot be reordered", | ||
| 2692 | )); | ||
| 2693 | } | ||
| 2694 | if sequence.windows(2).all(|pair| pair[0].1 < pair[1].1) { | ||
| 2695 | return Ok(sequence.into_iter().map(|(id, _)| id).collect()); | ||
| 2696 | } | ||
| 2697 | // Longest increasing subsequence over `after` positions, forced through immovable items. | ||
| 2698 | let n = sequence.len(); | ||
| 2699 | let mut best = vec![1usize; n]; | ||
| 2700 | let mut previous = vec![usize::MAX; n]; | ||
| 2701 | for i in 0..n { | ||
| 2702 | let (id, at) = sequence[i]; | ||
| 2703 | let mandatory_before = sequence[..i] | ||
| 2704 | .iter() | ||
| 2705 | .filter(|(other, _)| !movable(*other)) | ||
| 2706 | .map(|(_, at)| *at) | ||
| 2707 | .max(); | ||
| 2708 | if movable(id) && mandatory_before.is_some_and(|m| m > at) { | ||
| 2709 | best[i] = 0; | ||
| 2710 | continue; | ||
| 2711 | } | ||
| 2712 | let mandatory_after = sequence[i + 1..] | ||
| 2713 | .iter() | ||
| 2714 | .filter(|(other, _)| !movable(*other)) | ||
| 2715 | .map(|(_, at)| *at) | ||
| 2716 | .min(); | ||
| 2717 | if movable(id) && mandatory_after.is_some_and(|m| m < at) { | ||
| 2718 | best[i] = 0; | ||
| 2719 | continue; | ||
| 2720 | } | ||
| 2721 | for j in 0..i { | ||
| 2722 | if best[j] > 0 && sequence[j].1 < at && best[j] + 1 > best[i] { | ||
| 2723 | best[i] = best[j] + 1; | ||
| 2724 | previous[i] = j; | ||
| 2725 | } | ||
| 2726 | } | ||
| 2727 | } | ||
| 2728 | let mut kept = BTreeSet::new(); | ||
| 2729 | if let Some((mut i, _)) = best | ||
| 2730 | .iter() | ||
| 2731 | .enumerate() | ||
| 2732 | .max_by_key(|(i, b)| (**b, usize::MAX - i)) | ||
| 2733 | && best[i] > 0 | ||
| 2734 | { | ||
| 2735 | loop { | ||
| 2736 | kept.insert(sequence[i].0); | ||
| 2737 | if previous[i] == usize::MAX { | ||
| 2738 | break; | ||
| 2739 | } | ||
| 2740 | i = previous[i]; | ||
| 2741 | } | ||
| 2742 | } | ||
| 2743 | for (id, _) in &sequence { | ||
| 2744 | if !movable(*id) && !kept.contains(id) { | ||
| 2745 | return Err(invalid( | ||
| 2746 | "Images and unsupported objects cannot be reordered", | ||
| 2747 | )); | ||
| 2748 | } | ||
| 2749 | } | ||
| 2750 | Ok(kept) | ||
| 2751 | } | ||
| 2752 | |||
| 2753 | /// The format of the span containing the UTF-16 position `at` (the last span at the end). | ||
| 2754 | fn format_in(paragraph: &super::Paragraph, at: u32) -> Result<&Format, Error> { | ||
| 2755 | let byte = paragraph.byte_offset(at)?; | ||
| 2756 | let spans = paragraph.spans(); | ||
| 2757 | let index = spans | ||
| 2758 | .partition_point(|span| span.end <= byte) | ||
| 2759 | .min(spans.len() - 1); | ||
| 2760 | Ok(&spans[index].format) | ||
| 2761 | } | ||
| 2762 | |||
| 2763 | /// The smallest UTF-16 range whose replacement turns `before` into `after`. | ||
| 2764 | fn text_edit(before: &str, after: &str) -> Result<(Range<u32>, String), Error> { | ||
| 2765 | let prefix = before | ||
| 2766 | .char_indices() | ||
| 2767 | .zip(after.chars()) | ||
| 2768 | .take_while(|((_, a), b)| a == b) | ||
| 2769 | .map(|((i, a), _)| i + a.len_utf8()) | ||
| 2770 | .last() | ||
| 2771 | .unwrap_or(0); | ||
| 2772 | let suffix = before[prefix..] | ||
| 2773 | .chars() | ||
| 2774 | .rev() | ||
| 2775 | .zip(after[prefix..].chars().rev()) | ||
| 2776 | .take_while(|(a, b)| a == b) | ||
| 2777 | .map(|(a, _)| a.len_utf8()) | ||
| 2778 | .sum::<usize>(); | ||
| 2779 | let units = |s: &str| -> Result<u32, Error> { | ||
| 2780 | u32::try_from(s.encode_utf16().count()) | ||
| 2781 | .map_err(|_| invalid("Text exceeds UTF-16 offset range")) | ||
| 2782 | }; | ||
| 2783 | let start = units(&before[..prefix])?; | ||
| 2784 | let end = start + units(&before[prefix..before.len() - suffix])?; | ||
| 2785 | Ok((start..end, after[prefix..after.len() - suffix].to_owned())) | ||
| 2786 | } | ||
| 2787 | |||
| 2788 | /// Explicit attributes turning `current` into `target`; unsupported differences are errors. | ||
| 2789 | /// A `fresh` text object was inserted by this edit, so an unspecified target value keeps | ||
| 2790 | /// the insertion's default instead of demanding an inherited value the image cannot restore. | ||
| 2791 | /// Language tags are retained as stored because the model has no way to author them. | ||
| 2792 | fn attributes(current: &Format, target: &Format, fresh: bool) -> Result<Vec<TextAttribute>, Error> { | ||
| 2793 | let mut out = Vec::new(); | ||
| 2794 | let inherited = || invalid("Inherited character formatting cannot be restored"); | ||
| 2795 | // An absent flag and an explicit false are the same formatting. | ||
| 2796 | macro_rules! boolean { | ||
| 2797 | ($field:ident, $variant:ident) => { | ||
| 2798 | if current.$field.unwrap_or(false) != target.$field.unwrap_or(false) { | ||
| 2799 | out.push(TextAttribute::$variant(target.$field.unwrap_or(false))); | ||
| 2800 | } | ||
| 2801 | }; | ||
| 2802 | } | ||
| 2803 | boolean!(bold, Bold); | ||
| 2804 | boolean!(italic, Italic); | ||
| 2805 | boolean!(underline, Underline); | ||
| 2806 | boolean!(strike, Strike); | ||
| 2807 | boolean!(superscript, Superscript); | ||
| 2808 | boolean!(subscript, Subscript); | ||
| 2809 | boolean!(hidden, Hidden); | ||
| 2810 | boolean!(hyperlink, Hyperlink); | ||
| 2811 | boolean!(hyperlink_label, HyperlinkLabel); | ||
| 2812 | if current.font != target.font { | ||
| 2813 | match &target.font { | ||
| 2814 | Some(font) => out.push(TextAttribute::Font(font.clone())), | ||
| 2815 | None if fresh => {} | ||
| 2816 | None => return Err(inherited()), | ||
| 2817 | } | ||
| 2818 | } | ||
| 2819 | if current.font_size != target.font_size { | ||
| 2820 | match target.font_size { | ||
| 2821 | Some(size) => out.push(TextAttribute::FontSize(size)), | ||
| 2822 | None if fresh => {} | ||
| 2823 | None => return Err(inherited()), | ||
| 2824 | } | ||
| 2825 | } | ||
| 2826 | let color = | ||
| 2827 | |value: u32| (value != 0xff000000).then(|| value.to_le_bytes()[..3].try_into().unwrap()); | ||
| 2828 | if current.color != target.color { | ||
| 2829 | match target.color { | ||
| 2830 | Some(value) => out.push(TextAttribute::Color(color(value))), | ||
| 2831 | None if fresh => {} | ||
| 2832 | None => return Err(inherited()), | ||
| 2833 | } | ||
| 2834 | } | ||
| 2835 | if current.highlight != target.highlight { | ||
| 2836 | match target.highlight { | ||
| 2837 | Some(value) => out.push(TextAttribute::Highlight(color(value))), | ||
| 2838 | None if fresh => {} | ||
| 2839 | None => return Err(inherited()), | ||
| 2840 | } | ||
| 2841 | } | ||
| 2842 | if current.language != target.language { | ||
| 2843 | match target.language { | ||
| 2844 | Some(language) => out.push(TextAttribute::Language(language)), | ||
| 2845 | None if fresh => {} | ||
| 2846 | None => return Err(inherited()), | ||
| 2847 | } | ||
| 2848 | } | ||
| 2849 | // An absent value and its stored default are the same formatting. | ||
| 2850 | let flag = |a: Option<bool>, b: Option<bool>| a.unwrap_or(false) == b.unwrap_or(false); | ||
| 2851 | let points = |a: Option<f32>, b: Option<f32>| a.unwrap_or(0.0) == b.unwrap_or(0.0); | ||
| 2852 | let same_rest = flag(current.math, target.math) | ||
| 2853 | && flag(current.embedded_object, target.embedded_object) | ||
| 2854 | && current.alignment.unwrap_or(0) == target.alignment.unwrap_or(0) | ||
| 2855 | && flag(current.rtl, target.rtl) | ||
| 2856 | && points(current.space_before, target.space_before) | ||
| 2857 | && points(current.space_after, target.space_after) | ||
| 2858 | && points(current.line_spacing, target.line_spacing) | ||
| 2859 | && points(current.list_spacing, target.list_spacing); | ||
| 2860 | if !same_rest { | ||
| 2861 | return Err(invalid( | ||
| 2862 | "Fields and paragraph spacing cannot be edited through the page model", | ||
| 2863 | )); | ||
| 2864 | } | ||
| 2865 | Ok(out) | ||
| 2866 | } | ||
| 2867 | |||
| 2868 | /// Payload identities the file-data store of `store` declares, in order. | 1430 | /// Payload identities the file-data store of `store` declares, in order. |
| 2869 | pub(crate) fn declared_payloads(store: &Store<'_>) -> Vec<[u8; 16]> { | 1431 | pub(crate) fn declared_payloads(store: &Store<'_>) -> Vec<[u8; 16]> { |
| 2870 | store | 1432 | store |
| ... | @@ -3002,7 +1564,10 @@ fn remap(object: &mut PropertyObject, rename: &BTreeMap<ExGuid, ExGuid>) -> Resu | ... | @@ -3002,7 +1564,10 @@ fn remap(object: &mut PropertyObject, rename: &BTreeMap<ExGuid, ExGuid>) -> Resu |
| 3002 | #[cfg(test)] | 1564 | #[cfg(test)] |
| 3003 | mod tests { | 1565 | mod tests { |
| 3004 | use super::*; | 1566 | use super::*; |
| 3005 | use crate::page::{PageParagraph, Paragraph, TextObject, text::new_id}; | 1567 | use crate::{ |
| 1568 | document::Format, | ||
| 1569 | page::{PageParagraph, Paragraph, TextObject, text::new_id}, | ||
| 1570 | }; | ||
| 3006 | 1571 | ||
| 3007 | /// A new page in a new section and the page with `count` paragraphs added in an outline. | 1572 | /// A new page in a new section and the page with `count` paragraphs added in an outline. |
| 3008 | fn generated(count: usize) -> (Vec<u8>, ExGuid, Page) { | 1573 | fn generated(count: usize) -> (Vec<u8>, ExGuid, Page) { |
crates/onestore/src/pages.rs+367-244| ... | @@ -2,6 +2,7 @@ use crate::{ | ... | @@ -2,6 +2,7 @@ use crate::{ |
| 2 | Error, ExGuid, | 2 | Error, ExGuid, |
| 3 | create::{current_timestamps, properties, string}, | 3 | create::{current_timestamps, properties, string}, |
| 4 | document::{Document, FieldValue, Kind}, | 4 | document::{Document, FieldValue, Kind}, |
| 5 | op::content::{NATIVE_INDENTS, measurement_bytes}, | ||
| 5 | write::{PropertyObject, RevisionEdit, fresh_guid, write_revisions}, | 6 | write::{PropertyObject, RevisionEdit, fresh_guid, write_revisions}, |
| 6 | }; | 7 | }; |
| 7 | use serde::{Deserialize, Serialize}; | 8 | use serde::{Deserialize, Serialize}; |
| ... | @@ -129,11 +130,101 @@ impl PageEdit { | ... | @@ -129,11 +130,101 @@ impl PageEdit { |
| 129 | } | 130 | } |
| 130 | } | 131 | } |
| 131 | 132 | ||
| 133 | /// What page edits read of one page space: its metadata element and the stored manifest | ||
| 134 | /// and metadata objects. | ||
| 135 | pub(crate) struct PageParts<'a> { | ||
| 136 | pub metadata: crate::document::Element<'a>, | ||
| 137 | pub manifest: (ExGuid, PropertyObject), | ||
| 138 | pub stored_metadata: (ExGuid, PropertyObject), | ||
| 139 | } | ||
| 140 | |||
| 132 | pub(crate) fn edit_pages( | 141 | pub(crate) fn edit_pages( |
| 133 | source: &[u8], | 142 | source: &[u8], |
| 134 | edits: &[PageEdit], | 143 | edits: &[PageEdit], |
| 135 | removals: &[ExGuid], | 144 | removals: &[ExGuid], |
| 136 | ) -> Result<Vec<u8>, Error> { | 145 | ) -> Result<Vec<u8>, Error> { |
| 146 | write_revisions(source, |index| { | ||
| 147 | let document = Document::parse(index)?; | ||
| 148 | let pages: Vec<ExGuid> = document.pages()?.into_iter().map(|(sid, _)| sid).collect(); | ||
| 149 | let root = document.active(document.root)?; | ||
| 150 | let raw = index.resolve_active(document.root)?; | ||
| 151 | let parts = |sid: ExGuid| -> Result<PageParts<'_>, Error> { | ||
| 152 | let page = document.active(sid)?; | ||
| 153 | let revision = index.resolve_active(sid)?; | ||
| 154 | let metadata = page | ||
| 155 | .roots | ||
| 156 | .get(&2) | ||
| 157 | .and_then(|id| page.nodes.get(id)) | ||
| 158 | .ok_or_else(|| invalid("Page metadata is unavailable"))?; | ||
| 159 | let (manifest, stored) = (revision.roots[&1], revision.roots[&2]); | ||
| 160 | Ok(PageParts { | ||
| 161 | metadata: metadata.clone(), | ||
| 162 | manifest: (manifest, PropertyObject::from_object(&revision.objects[&manifest])?), | ||
| 163 | stored_metadata: (stored, PropertyObject::from_object(&revision.objects[&stored])?), | ||
| 164 | }) | ||
| 165 | }; | ||
| 166 | let Some(changes) = page_changes(document.root, root, &raw, &pages, parts, edits, removals)? else { | ||
| 167 | return Ok(BTreeMap::new()); | ||
| 168 | }; | ||
| 169 | Ok(changes | ||
| 170 | .into_iter() | ||
| 171 | .map(|(space, changes)| (space, RevisionEdit::Update(changes))) | ||
| 172 | .collect()) | ||
| 173 | }) | ||
| 174 | } | ||
| 175 | |||
| 176 | /// `edit_pages` on a section kept open: the objects each changed space stores. | ||
| 177 | pub(crate) fn section_changes( | ||
| 178 | section: &mut crate::Section<'_>, | ||
| 179 | edits: &[PageEdit], | ||
| 180 | removals: &[ExGuid], | ||
| 181 | ) -> Result<Vec<(ExGuid, crate::active::Changes)>, Error> { | ||
| 182 | let root = section.root(); | ||
| 183 | let (view, raw) = { | ||
| 184 | let page = section.active(root)?; | ||
| 185 | (page.view.clone(), page.live.revision.clone()) | ||
| 186 | }; | ||
| 187 | let section_id = *view | ||
| 188 | .roots | ||
| 189 | .get(&1) | ||
| 190 | .ok_or_else(|| invalid("Section root is unavailable"))?; | ||
| 191 | let pages: Vec<ExGuid> = view.nodes[&section_id] | ||
| 192 | .children | ||
| 193 | .iter() | ||
| 194 | .flat_map(|series| view.nodes[series].spaces.clone()) | ||
| 195 | .collect(); | ||
| 196 | let section = &*section; | ||
| 197 | let parts = |sid: ExGuid| -> Result<PageParts<'_>, Error> { | ||
| 198 | let revision = section.revision(sid)?; | ||
| 199 | let (manifest, stored) = (revision.roots[&1], revision.roots[&2]); | ||
| 200 | Ok(PageParts { | ||
| 201 | metadata: crate::document::Element::parse_with( | ||
| 202 | &revision.objects[&stored], | ||
| 203 | crate::FileType::Section, | ||
| 204 | &mut |_| Err(invalid("Page metadata holds no payload")), | ||
| 205 | )?, | ||
| 206 | manifest: (manifest, PropertyObject::from_object(&revision.objects[&manifest])?), | ||
| 207 | stored_metadata: (stored, PropertyObject::from_object(&revision.objects[&stored])?), | ||
| 208 | }) | ||
| 209 | }; | ||
| 210 | Ok(page_changes(root, &view, &raw, &pages, parts, edits, removals)? | ||
| 211 | .unwrap_or_default() | ||
| 212 | .into_iter() | ||
| 213 | .filter(|(_, changes)| !changes.is_empty()) | ||
| 214 | .collect()) | ||
| 215 | } | ||
| 216 | |||
| 217 | /// The objects page moves, indentation and removals change in each space; none when the | ||
| 218 | /// order and levels stay. | ||
| 219 | fn page_changes<'a>( | ||
| 220 | root: ExGuid, | ||
| 221 | view: &crate::document::Revision<'_>, | ||
| 222 | raw: &crate::ResolvedRevision<'_>, | ||
| 223 | pages: &[ExGuid], | ||
| 224 | parts: impl Fn(ExGuid) -> Result<PageParts<'a>, Error>, | ||
| 225 | edits: &[PageEdit], | ||
| 226 | removals: &[ExGuid], | ||
| 227 | ) -> Result<Option<BTreeMap<ExGuid, crate::active::Changes>>, Error> { | ||
| 137 | if !edits.is_empty() && !removals.is_empty() { | 228 | if !edits.is_empty() && !removals.is_empty() { |
| 138 | return Err(invalid( | 229 | return Err(invalid( |
| 139 | "Choose page placement or page removal for one operation", | 230 | "Choose page placement or page removal for one operation", |
| ... | @@ -153,252 +244,242 @@ pub(crate) fn edit_pages( | ... | @@ -153,252 +244,242 @@ pub(crate) fn edit_pages( |
| 153 | )); | 244 | )); |
| 154 | } | 245 | } |
| 155 | } | 246 | } |
| 156 | write_revisions(source, |index| { | 247 | let section_id = view.roots[&1]; |
| 157 | let document = Document::parse(index)?; | 248 | let section_node = &view.nodes[&section_id]; |
| 158 | let pages = document.pages()?; | 249 | if !matches!(section_node.kind, Kind::Section { .. }) |
| 159 | let view = document.active(document.root)?; | 250 | || section_node.extra[0] |
| 160 | let section_id = view.roots[&1]; | 251 | .iter() |
| 161 | let section_node = &view.nodes[&section_id]; | 252 | .any(|field| field.id == 0x88001cde) |
| 162 | if !matches!(section_node.kind, Kind::Section { .. }) | 253 | { |
| 163 | || section_node.extra[0] | 254 | return Err(invalid("Choose an editable section for page editing")); |
| 255 | } | ||
| 256 | let mut order = Vec::new(); | ||
| 257 | let mut levels = BTreeMap::new(); | ||
| 258 | let mut page_guids = BTreeMap::new(); | ||
| 259 | let mut stored = BTreeMap::new(); | ||
| 260 | for sid in pages { | ||
| 261 | let page = parts(*sid)?; | ||
| 262 | let Kind::Metadata { level, .. } = page.metadata.kind else { | ||
| 263 | return Err(invalid("Choose ordinary pages with page metadata")); | ||
| 264 | }; | ||
| 265 | let level = level.unwrap_or(1); | ||
| 266 | if !(1..=3).contains(&level) || levels.insert(*sid, level).is_some() { | ||
| 267 | return Err(invalid( | ||
| 268 | "Each page space must contain one page with a valid level", | ||
| 269 | )); | ||
| 270 | } | ||
| 271 | if page_guids.insert(metadata_guid(&page.metadata)?, *sid).is_some() { | ||
| 272 | return Err(invalid("Pages must have distinct metadata identifiers")); | ||
| 273 | } | ||
| 274 | order.push(*sid); | ||
| 275 | stored.insert(*sid, page); | ||
| 276 | } | ||
| 277 | let original_order = order.clone(); | ||
| 278 | let original_levels = levels.clone(); | ||
| 279 | let mut series_by_head = BTreeMap::new(); | ||
| 280 | let mut copies = BTreeMap::new(); | ||
| 281 | let mut copy_ids = BTreeSet::new(); | ||
| 282 | for oid in &section_node.children { | ||
| 283 | let series = &view.nodes[oid]; | ||
| 284 | let Some(head) = series.spaces.first() else { | ||
| 285 | return Err(invalid("A page series must contain at least one page")); | ||
| 286 | }; | ||
| 287 | if levels.get(head) != Some(&1) | ||
| 288 | || series.spaces[1..] | ||
| 164 | .iter() | 289 | .iter() |
| 165 | .any(|field| field.id == 0x88001cde) | 290 | .any(|sid| levels.get(sid).is_none_or(|level| *level == 1)) |
| 166 | { | 291 | { |
| 167 | return Err(invalid("Choose an editable section for page editing")); | 292 | return Err(invalid( |
| 293 | "A page series must start with its only top-level page", | ||
| 294 | )); | ||
| 168 | } | 295 | } |
| 169 | let mut order = Vec::new(); | 296 | if let Some(retained) = series.spaces.iter().find(|sid| !removed.contains(sid)) { |
| 170 | let mut levels = BTreeMap::new(); | 297 | series_by_head.insert(*retained, *oid); |
| 171 | let mut page_guids = BTreeMap::new(); | ||
| 172 | for (sid, _) in &pages { | ||
| 173 | let page = document.active(*sid)?; | ||
| 174 | let Some(metadata) = page.roots.get(&2).and_then(|id| page.nodes.get(id)) else { | ||
| 175 | return Err(invalid("Page metadata is unavailable")); | ||
| 176 | }; | ||
| 177 | let Kind::Metadata { level, .. } = metadata.kind else { | ||
| 178 | return Err(invalid("Choose ordinary pages with page metadata")); | ||
| 179 | }; | ||
| 180 | let level = level.unwrap_or(1); | ||
| 181 | if !(1..=3).contains(&level) || levels.insert(*sid, level).is_some() { | ||
| 182 | return Err(invalid( | ||
| 183 | "Each page space must contain one page with a valid level", | ||
| 184 | )); | ||
| 185 | } | ||
| 186 | if page_guids.insert(metadata_guid(metadata)?, *sid).is_some() { | ||
| 187 | return Err(invalid("Pages must have distinct metadata identifiers")); | ||
| 188 | } | ||
| 189 | order.push(*sid); | ||
| 190 | } | 298 | } |
| 191 | let original_order = order.clone(); | 299 | if let Some(field) = series.extra[0].iter().find(|field| field.id == 0x24003442) { |
| 192 | let original_levels = levels.clone(); | 300 | let FieldValue::Objects(ids) = &field.value else { |
| 193 | let mut series_by_head = BTreeMap::new(); | 301 | return Err(invalid("Page metadata copies must be object references")); |
| 194 | let mut copies = BTreeMap::new(); | ||
| 195 | let mut copy_ids = BTreeSet::new(); | ||
| 196 | for oid in &section_node.children { | ||
| 197 | let series = &view.nodes[oid]; | ||
| 198 | let Some(head) = series.spaces.first() else { | ||
| 199 | return Err(invalid("A page series must contain at least one page")); | ||
| 200 | }; | 302 | }; |
| 201 | if levels.get(head) != Some(&1) | 303 | if ids.len() != series.spaces.len() { |
| 202 | || series.spaces[1..] | 304 | return Err(invalid("Page metadata copies must match their series")); |
| 203 | .iter() | ||
| 204 | .any(|sid| levels.get(sid).is_none_or(|level| *level == 1)) | ||
| 205 | { | ||
| 206 | return Err(invalid( | ||
| 207 | "A page series must start with its only top-level page", | ||
| 208 | )); | ||
| 209 | } | ||
| 210 | if let Some(retained) = series.spaces.iter().find(|sid| !removed.contains(sid)) { | ||
| 211 | series_by_head.insert(*retained, *oid); | ||
| 212 | } | 305 | } |
| 213 | if let Some(field) = series.extra[0].iter().find(|field| field.id == 0x24003442) { | 306 | for id in ids { |
| 214 | let FieldValue::Objects(ids) = &field.value else { | 307 | if !copy_ids.insert(*id) |
| 215 | return Err(invalid("Page metadata copies must be object references")); | 308 | || !matches!( |
| 216 | }; | 309 | view.nodes.get(id).map(|n| &n.kind), |
| 217 | if ids.len() != series.spaces.len() { | 310 | Some(Kind::Metadata { .. }) |
| 218 | return Err(invalid("Page metadata copies must match their series")); | 311 | ) |
| 312 | { | ||
| 313 | return Err(invalid("Each page needs its own ordinary metadata copy")); | ||
| 219 | } | 314 | } |
| 220 | for id in ids { | 315 | let sid = page_guids |
| 221 | if !copy_ids.insert(*id) | 316 | .get(&metadata_guid(&view.nodes[id])?) |
| 222 | || !matches!( | 317 | .filter(|sid| series.spaces.contains(sid)) |
| 223 | view.nodes.get(id).map(|n| &n.kind), | 318 | .ok_or_else(|| { |
| 224 | Some(Kind::Metadata { .. }) | 319 | invalid("Metadata copy must identify a page in its series") |
| 225 | ) | 320 | })?; |
| 226 | { | 321 | if copies.insert(*sid, *id).is_some() { |
| 227 | return Err(invalid("Each page needs its own ordinary metadata copy")); | 322 | return Err(invalid("Each page needs its own ordinary metadata copy")); |
| 228 | } | ||
| 229 | let sid = page_guids | ||
| 230 | .get(&metadata_guid(&view.nodes[id])?) | ||
| 231 | .filter(|sid| series.spaces.contains(sid)) | ||
| 232 | .ok_or_else(|| { | ||
| 233 | invalid("Metadata copy must identify a page in its series") | ||
| 234 | })?; | ||
| 235 | if copies.insert(*sid, *id).is_some() { | ||
| 236 | return Err(invalid("Each page needs its own ordinary metadata copy")); | ||
| 237 | } | ||
| 238 | } | 323 | } |
| 239 | } | 324 | } |
| 240 | } | 325 | } |
| 241 | for sid in &removed { | 326 | } |
| 242 | if !levels.contains_key(sid) { | 327 | for sid in &removed { |
| 243 | return Err(invalid("The selected page is no longer in the section")); | 328 | if !levels.contains_key(sid) { |
| 244 | } | 329 | return Err(invalid("The selected page is no longer in the section")); |
| 245 | } | ||
| 246 | order.retain(|sid| !removed.contains(sid)); | ||
| 247 | if !removed.is_empty() | ||
| 248 | && let Some(first) = order.first() | ||
| 249 | { | ||
| 250 | levels.insert(*first, 1); | ||
| 251 | } | 330 | } |
| 252 | for edit in edits { | 331 | } |
| 253 | let Some(at) = order.iter().position(|sid| *sid == edit.space) else { | 332 | order.retain(|sid| !removed.contains(sid)); |
| 254 | return Err(invalid("The selected page is no longer in the section")); | 333 | if !removed.is_empty() |
| 334 | && let Some(first) = order.first() | ||
| 335 | { | ||
| 336 | levels.insert(*first, 1); | ||
| 337 | } | ||
| 338 | for edit in edits { | ||
| 339 | let Some(at) = order.iter().position(|sid| *sid == edit.space) else { | ||
| 340 | return Err(invalid("The selected page is no longer in the section")); | ||
| 341 | }; | ||
| 342 | levels.insert(edit.space, edit.level); | ||
| 343 | if let PagePosition::Before(before) = edit.position { | ||
| 344 | order.remove(at); | ||
| 345 | let position = match before { | ||
| 346 | None => order.len(), | ||
| 347 | Some(before) => { | ||
| 348 | order.iter().position(|sid| *sid == before).ok_or_else(|| { | ||
| 349 | invalid("The movement anchor is no longer in the section") | ||
| 350 | })? | ||
| 351 | } | ||
| 255 | }; | 352 | }; |
| 256 | levels.insert(edit.space, edit.level); | 353 | order.insert(position, edit.space); |
| 257 | if let PagePosition::Before(before) = edit.position { | ||
| 258 | order.remove(at); | ||
| 259 | let position = match before { | ||
| 260 | None => order.len(), | ||
| 261 | Some(before) => { | ||
| 262 | order.iter().position(|sid| *sid == before).ok_or_else(|| { | ||
| 263 | invalid("The movement anchor is no longer in the section") | ||
| 264 | })? | ||
| 265 | } | ||
| 266 | }; | ||
| 267 | order.insert(position, edit.space); | ||
| 268 | } | ||
| 269 | } | 354 | } |
| 270 | if order.first().is_some_and(|sid| levels[sid] != 1) { | 355 | } |
| 271 | return Err(invalid( | 356 | if order.first().is_some_and(|sid| levels[sid] != 1) { |
| 272 | "The section's first page must have indentation level 1", | 357 | return Err(invalid( |
| 273 | )); | 358 | "The section's first page must have indentation level 1", |
| 359 | )); | ||
| 360 | } | ||
| 361 | if order == original_order && levels == original_levels { | ||
| 362 | return Ok(None); | ||
| 363 | } | ||
| 364 | let mut groups: Vec<Vec<ExGuid>> = Vec::new(); | ||
| 365 | for sid in order { | ||
| 366 | if levels[&sid] == 1 { | ||
| 367 | groups.push(Vec::new()); | ||
| 274 | } | 368 | } |
| 275 | if order == original_order && levels == original_levels { | 369 | groups.last_mut().unwrap().push(sid); |
| 276 | return Ok(BTreeMap::new()); | 370 | } |
| 371 | let mut changes = BTreeMap::new(); | ||
| 372 | for sid in &removed { | ||
| 373 | let page = &stored[sid]; | ||
| 374 | let (manifest_id, mut manifest) = page.manifest.clone(); | ||
| 375 | if manifest.jcid != 0x60037 { | ||
| 376 | return Err(invalid("Choose ordinary pages for removal")); | ||
| 277 | } | 377 | } |
| 278 | let raw = index.resolve_active(document.root)?; | 378 | manifest.bytes = properties(&[])?; |
| 279 | let mut groups: Vec<Vec<ExGuid>> = Vec::new(); | 379 | let (metadata_id, mut metadata) = page.stored_metadata.clone(); |
| 280 | for sid in order { | 380 | metadata.set(&[(0x88001de9, &[])])?; |
| 281 | if levels[&sid] == 1 { | 381 | changes.insert( |
| 282 | groups.push(Vec::new()); | 382 | *sid, |
| 383 | BTreeMap::from([(manifest_id, manifest), (metadata_id, metadata)]), | ||
| 384 | ); | ||
| 385 | } | ||
| 386 | let mut replacements = BTreeMap::new(); | ||
| 387 | let mut children = Vec::new(); | ||
| 388 | for spaces in groups { | ||
| 389 | let head = spaces[0]; | ||
| 390 | let old_id = series_by_head.get(&head).copied(); | ||
| 391 | let (id, mut series) = if let Some(id) = old_id { | ||
| 392 | (id, PropertyObject::from_object(&raw.objects[&id])?) | ||
| 393 | } else { | ||
| 394 | let edit = selected | ||
| 395 | .get(&head) | ||
| 396 | .ok_or_else(|| invalid("A new series must start at an edited page"))?; | ||
| 397 | let id = ExGuid { | ||
| 398 | guid: edit.guid, | ||
| 399 | n: 1, | ||
| 400 | }; | ||
| 401 | if raw.objects.contains_key(&id) { | ||
| 402 | return Err(invalid("The new page-series identity already exists")); | ||
| 283 | } | 403 | } |
| 284 | groups.last_mut().unwrap().push(sid); | 404 | ( |
| 405 | id, | ||
| 406 | PropertyObject { | ||
| 407 | jcid: 0x60008, | ||
| 408 | bytes: properties(&[(0x1c001c30, edit.guid.to_vec())])?, | ||
| 409 | global_ids: Arc::new(BTreeMap::from([(0, edit.guid)])), | ||
| 410 | }, | ||
| 411 | ) | ||
| 412 | }; | ||
| 413 | children.push(id); | ||
| 414 | let membership_changed = old_id.is_none_or(|old| view.nodes[&old].spaces != spaces); | ||
| 415 | if !membership_changed | ||
| 416 | && !spaces.iter().any(|sid| { | ||
| 417 | levels[sid] != original_levels[sid] | ||
| 418 | || copies.get(sid).is_some_and(|id| { | ||
| 419 | matches!(view.nodes[id].kind, Kind::Metadata { level, .. } | ||
| 420 | if level.unwrap_or(1) != levels[sid]) | ||
| 421 | }) | ||
| 422 | }) | ||
| 423 | { | ||
| 424 | continue; | ||
| 285 | } | 425 | } |
| 286 | let mut changes = BTreeMap::new(); | 426 | let mut metadata_ids = Vec::new(); |
| 287 | for sid in &removed { | 427 | for sid in &spaces { |
| 288 | let page = index.resolve_active(*sid)?; | 428 | let (page_id, original) = stored[sid].stored_metadata.clone(); |
| 289 | let manifest_id = page.roots[&1]; | 429 | if old_id.is_none() && *sid == head { |
| 290 | let mut manifest = PropertyObject::from_object(&page.objects[&manifest_id])?; | 430 | series.copy_property(&original, 0x18001c65)?; |
| 291 | if manifest.jcid != 0x60037 { | ||
| 292 | return Err(invalid("Choose ordinary pages for removal")); | ||
| 293 | } | 431 | } |
| 294 | manifest.bytes = properties(&[])?; | 432 | if levels[sid] != original_levels[sid] { |
| 295 | let metadata_id = page.roots[&2]; | 433 | let mut metadata = original.clone(); |
| 296 | let mut metadata = PropertyObject::from_object(&page.objects[&metadata_id])?; | 434 | metadata.set(&[(0x14001dff, &levels[sid].to_le_bytes())])?; |
| 297 | metadata.set(&[(0x88001de9, &[])])?; | 435 | changes.insert(*sid, BTreeMap::from([(page_id, metadata)])); |
| 298 | changes.insert( | ||
| 299 | *sid, | ||
| 300 | RevisionEdit::Update(BTreeMap::from([ | ||
| 301 | (manifest_id, manifest), | ||
| 302 | (metadata_id, metadata), | ||
| 303 | ])), | ||
| 304 | ); | ||
| 305 | } | ||
| 306 | let mut replacements = BTreeMap::new(); | ||
| 307 | let mut children = Vec::new(); | ||
| 308 | for spaces in groups { | ||
| 309 | let head = spaces[0]; | ||
| 310 | let old_id = series_by_head.get(&head).copied(); | ||
| 311 | let (id, mut series) = if let Some(id) = old_id { | ||
| 312 | (id, PropertyObject::from_object(&raw.objects[&id])?) | ||
| 313 | } else { | ||
| 314 | let edit = selected | ||
| 315 | .get(&head) | ||
| 316 | .ok_or_else(|| invalid("A new series must start at an edited page"))?; | ||
| 317 | let id = ExGuid { | ||
| 318 | guid: edit.guid, | ||
| 319 | n: 1, | ||
| 320 | }; | ||
| 321 | if raw.objects.contains_key(&id) { | ||
| 322 | return Err(invalid("The new page-series identity already exists")); | ||
| 323 | } | ||
| 324 | ( | ||
| 325 | id, | ||
| 326 | PropertyObject { | ||
| 327 | jcid: 0x60008, | ||
| 328 | bytes: properties(&[(0x1c001c30, edit.guid.to_vec())])?, | ||
| 329 | global_ids: Arc::new(BTreeMap::from([(0, edit.guid)])), | ||
| 330 | }, | ||
| 331 | ) | ||
| 332 | }; | ||
| 333 | children.push(id); | ||
| 334 | let membership_changed = old_id.is_none_or(|old| view.nodes[&old].spaces != spaces); | ||
| 335 | if !membership_changed | ||
| 336 | && !spaces.iter().any(|sid| { | ||
| 337 | levels[sid] != original_levels[sid] | ||
| 338 | || copies.get(sid).is_some_and(|id| { | ||
| 339 | matches!(view.nodes[id].kind, Kind::Metadata { level, .. } | ||
| 340 | if level.unwrap_or(1) != levels[sid]) | ||
| 341 | }) | ||
| 342 | }) | ||
| 343 | { | ||
| 344 | continue; | ||
| 345 | } | 436 | } |
| 346 | let mut metadata_ids = Vec::new(); | 437 | let copy_id = copies |
| 347 | for sid in &spaces { | 438 | .get(sid) |
| 348 | let page = index.resolve_active(*sid)?; | 439 | .copied() |
| 349 | let page_id = page.roots[&2]; | 440 | .unwrap_or_else(|| metadata_id(*sid)); |
| 350 | let original = PropertyObject::from_object(&page.objects[&page_id])?; | 441 | let mut copy = match raw.objects.get(&copy_id) { |
| 351 | if old_id.is_none() && *sid == head { | 442 | Some(object) => { |
| 352 | series.copy_property(&original, 0x18001c65)?; | 443 | if object.jcid != 0x20030 |
| 353 | } | 444 | || (!copies.contains_key(sid) && copy_ids.contains(&copy_id)) |
| 354 | if levels[sid] != original_levels[sid] { | 445 | { |
| 355 | let mut metadata = PropertyObject::from_object(&page.objects[&page_id])?; | 446 | return Err(invalid("Page metadata identities overlap")); |
| 356 | metadata.set(&[(0x14001dff, &levels[sid].to_le_bytes())])?; | ||
| 357 | changes.insert( | ||
| 358 | *sid, | ||
| 359 | RevisionEdit::Update(BTreeMap::from([(page_id, metadata)])), | ||
| 360 | ); | ||
| 361 | } | ||
| 362 | let copy_id = copies | ||
| 363 | .get(sid) | ||
| 364 | .copied() | ||
| 365 | .unwrap_or_else(|| metadata_id(*sid)); | ||
| 366 | let mut copy = match raw.objects.get(&copy_id) { | ||
| 367 | Some(object) => { | ||
| 368 | if object.jcid != 0x20030 | ||
| 369 | || (!copies.contains_key(sid) && copy_ids.contains(&copy_id)) | ||
| 370 | { | ||
| 371 | return Err(invalid("Page metadata identities overlap")); | ||
| 372 | } | ||
| 373 | PropertyObject::from_object(object)? | ||
| 374 | } | 447 | } |
| 375 | None => original, | 448 | PropertyObject::from_object(object)? |
| 376 | }; | ||
| 377 | copy.set(&[(0x14001dff, &levels[sid].to_le_bytes())])?; | ||
| 378 | copy.reference(copy_id)?; | ||
| 379 | if replacements.insert(copy_id, copy).is_some() { | ||
| 380 | return Err(invalid("Page metadata identities overlap")); | ||
| 381 | } | ||
| 382 | metadata_ids.push(copy_id); | ||
| 383 | } | ||
| 384 | if membership_changed || spaces.iter().any(|sid| !copies.contains_key(sid)) { | ||
| 385 | set_references(&mut series, 0x2c001d63, &spaces)?; | ||
| 386 | set_references(&mut series, 0x24003442, &metadata_ids)?; | ||
| 387 | if replacements.insert(id, series).is_some() { | ||
| 388 | return Err(invalid("Page-series and metadata identities overlap")); | ||
| 389 | } | 449 | } |
| 450 | None => original, | ||
| 451 | }; | ||
| 452 | copy.set(&[(0x14001dff, &levels[sid].to_le_bytes())])?; | ||
| 453 | copy.reference(copy_id)?; | ||
| 454 | if replacements.insert(copy_id, copy).is_some() { | ||
| 455 | return Err(invalid("Page metadata identities overlap")); | ||
| 390 | } | 456 | } |
| 457 | metadata_ids.push(copy_id); | ||
| 391 | } | 458 | } |
| 392 | if children != section_node.children { | 459 | if membership_changed || spaces.iter().any(|sid| !copies.contains_key(sid)) { |
| 393 | let mut parent = PropertyObject::from_object(&raw.objects[&section_id])?; | 460 | set_references(&mut series, 0x2c001d63, &spaces)?; |
| 394 | set_references(&mut parent, 0x24001c20, &children)?; | 461 | set_references(&mut series, 0x24003442, &metadata_ids)?; |
| 395 | replacements.insert(section_id, parent); | 462 | if replacements.insert(id, series).is_some() { |
| 463 | return Err(invalid("Page-series and metadata identities overlap")); | ||
| 464 | } | ||
| 396 | } | 465 | } |
| 397 | changes.insert(document.root, RevisionEdit::Update(replacements)); | 466 | } |
| 398 | Ok(changes) | 467 | if children != section_node.children { |
| 399 | }) | 468 | let mut parent = PropertyObject::from_object(&raw.objects[&section_id])?; |
| 469 | set_references(&mut parent, 0x24001c20, &children)?; | ||
| 470 | replacements.insert(section_id, parent); | ||
| 471 | } | ||
| 472 | changes.insert(root, replacements); | ||
| 473 | Ok(Some(changes)) | ||
| 400 | } | 474 | } |
| 401 | 475 | ||
| 476 | /// A page creation's changes to the root space, and the new space's roots and objects. | ||
| 477 | pub(crate) type Creation = ( | ||
| 478 | crate::active::Changes, | ||
| 479 | BTreeMap<u32, ExGuid>, | ||
| 480 | crate::active::Changes, | ||
| 481 | ); | ||
| 482 | |||
| 402 | /// An empty top-level page with stable identities and creation time. | 483 | /// An empty top-level page with stable identities and creation time. |
| 403 | /// Retain the intent across retries; an existing page identity rejects duplicate creation. | 484 | /// Retain the intent across retries; an existing page identity rejects duplicate creation. |
| 404 | #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] | 485 | #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] |
| ... | @@ -487,6 +568,38 @@ impl PageCreation { | ... | @@ -487,6 +568,38 @@ impl PageCreation { |
| 487 | let document = Document::parse(index)?; | 568 | let document = Document::parse(index)?; |
| 488 | document.pages()?; | 569 | document.pages()?; |
| 489 | let view = document.active(document.root)?; | 570 | let view = document.active(document.root)?; |
| 571 | let raw = index.resolve_active(document.root)?; | ||
| 572 | let (root, roots, objects) = self.creation(view, &raw)?; | ||
| 573 | Ok(BTreeMap::from([ | ||
| 574 | (document.root, RevisionEdit::Update(root)), | ||
| 575 | (self.space(), RevisionEdit::Create { roots, objects }), | ||
| 576 | ])) | ||
| 577 | }) | ||
| 578 | } | ||
| 579 | |||
| 580 | /// `apply` on a section kept open: the root space's changes, and the new space's roots | ||
| 581 | /// and objects. | ||
| 582 | pub(crate) fn changes( | ||
| 583 | &self, | ||
| 584 | section: &mut crate::Section<'_>, | ||
| 585 | ) -> Result<Creation, Error> { | ||
| 586 | self.validate()?; | ||
| 587 | if section.revision(self.space()).is_ok() { | ||
| 588 | return Err(invalid( | ||
| 589 | "This page identity already exists; reconcile the original creation", | ||
| 590 | )); | ||
| 591 | } | ||
| 592 | let root = section.root(); | ||
| 593 | let page = section.active(root)?; | ||
| 594 | self.creation(&page.view, &page.live.revision) | ||
| 595 | } | ||
| 596 | |||
| 597 | fn creation( | ||
| 598 | &self, | ||
| 599 | view: &crate::document::Revision<'_>, | ||
| 600 | raw: &crate::ResolvedRevision<'_>, | ||
| 601 | ) -> Result<Creation, Error> { | ||
| 602 | { | ||
| 490 | let section_id = view.roots[&1]; | 603 | let section_id = view.roots[&1]; |
| 491 | let section_node = &view.nodes[&section_id]; | 604 | let section_node = &view.nodes[&section_id]; |
| 492 | if !matches!(section_node.kind, Kind::Section { .. }) | 605 | if !matches!(section_node.kind, Kind::Section { .. }) |
| ... | @@ -506,7 +619,6 @@ impl PageCreation { | ... | @@ -506,7 +619,6 @@ impl PageCreation { |
| 506 | invalid("Insert before the first page of an existing series, or append") | 619 | invalid("Insert before the first page of an existing series, or append") |
| 507 | })?, | 620 | })?, |
| 508 | }; | 621 | }; |
| 509 | let raw = index.resolve_active(document.root)?; | ||
| 510 | let id = |n| ExGuid { guid: self.guid, n }; | 622 | let id = |n| ExGuid { guid: self.guid, n }; |
| 511 | let reference = |n: u32| n.to_le_bytes().to_vec(); | 623 | let reference = |n: u32| n.to_le_bytes().to_vec(); |
| 512 | let timestamp = ((u64::from(self.created) + 315532800 + 11644473600) * 10000000) | 624 | let timestamp = ((u64::from(self.created) + 315532800 + 11644473600) * 10000000) |
| ... | @@ -533,11 +645,15 @@ impl PageCreation { | ... | @@ -533,11 +645,15 @@ impl PageCreation { |
| 533 | (0x14001dff, reference(1)), | 645 | (0x14001dff, reference(1)), |
| 534 | (0x18001c65, timestamp.clone()), | 646 | (0x18001c65, timestamp.clone()), |
| 535 | ]; | 647 | ]; |
| 536 | let mut page = vec![ | 648 | let mut page = [ |
| 537 | modified(), | 649 | vec![ |
| 538 | (0x1c001d75, string(&self.author)), | 650 | modified(), |
| 539 | (0x1c001d3c, string("")), | 651 | (0x1c001d75, string(&self.author)), |
| 540 | ]; | 652 | (0x1c001d3c, string("")), |
| 653 | ], | ||
| 654 | crate::create::page_margins(), | ||
| 655 | ] | ||
| 656 | .concat(); | ||
| 541 | if self.title.is_some() { | 657 | if self.title.is_some() { |
| 542 | page.push((0x24001d5f, reference(13))); | 658 | page.push((0x24001d5f, reference(13))); |
| 543 | } | 659 | } |
| ... | @@ -556,6 +672,9 @@ impl PageCreation { | ... | @@ -556,6 +672,9 @@ impl PageCreation { |
| 556 | (0x24001c20, reference(14)), | 672 | (0x24001c20, reference(14)), |
| 557 | (0x14001c14, 0_f32.to_le_bytes().to_vec()), | 673 | (0x14001c14, 0_f32.to_le_bytes().to_vec()), |
| 558 | (0x14001c15, 0_f32.to_le_bytes().to_vec()), | 674 | (0x14001c15, 0_f32.to_le_bytes().to_vec()), |
| 675 | (0x14001c3e, reference(0x0009_000c)), | ||
| 676 | (0x14001c84, reference(0)), | ||
| 677 | (0x14001cf1, reference(0)), | ||
| 559 | ], | 678 | ], |
| 560 | ), | 679 | ), |
| 561 | ( | 680 | ( |
| ... | @@ -564,7 +683,18 @@ impl PageCreation { | ... | @@ -564,7 +683,18 @@ impl PageCreation { |
| 564 | vec![ | 683 | vec![ |
| 565 | modified(), | 684 | modified(), |
| 566 | (0x24001c20, reference(15)), | 685 | (0x24001c20, reference(15)), |
| 686 | (0x1c001c12, measurement_bytes(&NATIVE_INDENTS, 4)?), | ||
| 687 | (0x0c001c13, vec![0]), | ||
| 567 | (0x0c001c03, vec![1]), | 688 | (0x0c001c03, vec![1]), |
| 689 | (0x14001c1c, 0.6_f32.to_le_bytes().to_vec()), | ||
| 690 | (0x88001cf9, vec![]), | ||
| 691 | (0x88001cb2, vec![]), | ||
| 692 | (0x88001cb4, vec![]), | ||
| 693 | (0x88001c91, vec![]), | ||
| 694 | (0x14001cec, 4.5_f32.to_le_bytes().to_vec()), | ||
| 695 | (0x88001cff, vec![]), | ||
| 696 | (0x14001c3e, reference(0)), | ||
| 697 | (0x14001c84, reference(0xc)), | ||
| 568 | ], | 698 | ], |
| 569 | ), | 699 | ), |
| 570 | ( | 700 | ( |
| ... | @@ -577,6 +707,7 @@ impl PageCreation { | ... | @@ -577,6 +707,7 @@ impl PageCreation { |
| 577 | (0x20001d78, reference(17)), | 707 | (0x20001d78, reference(17)), |
| 578 | (0x20001d79, reference(17)), | 708 | (0x20001d79, reference(17)), |
| 579 | (0x14001d09, self.created.to_le_bytes().to_vec()), | 709 | (0x14001d09, self.created.to_le_bytes().to_vec()), |
| 710 | (0x88001cb2, vec![]), | ||
| 580 | (0x88001cb4, vec![]), | 711 | (0x88001cb4, vec![]), |
| 581 | ], | 712 | ], |
| 582 | ), | 713 | ), |
| ... | @@ -627,24 +758,16 @@ impl PageCreation { | ... | @@ -627,24 +758,16 @@ impl PageCreation { |
| 627 | references.extend_from_slice(&parent.reference(child)?); | 758 | references.extend_from_slice(&parent.reference(child)?); |
| 628 | } | 759 | } |
| 629 | parent.set(&[(0x24001c20, &references)])?; | 760 | parent.set(&[(0x24001c20, &references)])?; |
| 630 | Ok(BTreeMap::from([ | 761 | Ok(( |
| 631 | ( | 762 | BTreeMap::from([ |
| 632 | document.root, | 763 | (section_id, parent), |
| 633 | RevisionEdit::Update(BTreeMap::from([ | 764 | (id(2), series), |
| 634 | (section_id, parent), | 765 | (metadata_id, object(0x20030, metadata)?), |
| 635 | (id(2), series), | 766 | ]), |
| 636 | (metadata_id, object(0x20030, metadata)?), | 767 | BTreeMap::from([(1, id(10)), (2, id(11))]), |
| 637 | ])), | 768 | objects, |
| 638 | ), | 769 | )) |
| 639 | ( | 770 | } |
| 640 | self.space(), | ||
| 641 | RevisionEdit::Create { | ||
| 642 | roots: BTreeMap::from([(1, id(10)), (2, id(11))]), | ||
| 643 | objects, | ||
| 644 | }, | ||
| 645 | ), | ||
| 646 | ])) | ||
| 647 | }) | ||
| 648 | } | 771 | } |
| 649 | } | 772 | } |
| 650 | 773 |
crates/onestore/src/paragraph.rs+81-34| ... | @@ -85,6 +85,34 @@ impl ParagraphSplit { | ... | @@ -85,6 +85,34 @@ impl ParagraphSplit { |
| 85 | } | 85 | } |
| 86 | 86 | ||
| 87 | pub(crate) fn changes(&self, active: &ActivePage<'_>) -> Result<Changes, Error> { | 87 | pub(crate) fn changes(&self, active: &ActivePage<'_>) -> Result<Changes, Error> { |
| 88 | let lists = (5..256) | ||
| 89 | .map(|n| ExGuid { | ||
| 90 | guid: self.guid, | ||
| 91 | n, | ||
| 92 | }) | ||
| 93 | .collect::<Vec<_>>(); | ||
| 94 | let changes = self.changes_as(active, self.object(), self.text_object(), &lists)?; | ||
| 95 | let raw = &active.live.revision; | ||
| 96 | if changes | ||
| 97 | .keys() | ||
| 98 | .any(|id| id.guid == self.guid && raw.objects.contains_key(id)) | ||
| 99 | { | ||
| 100 | return Err(invalid( | ||
| 101 | "A split identity already exists; reconcile the existing edit", | ||
| 102 | )); | ||
| 103 | } | ||
| 104 | Ok(changes) | ||
| 105 | } | ||
| 106 | |||
| 107 | /// `changes` creating the right paragraph `paragraph` with text `right`, and a copy of | ||
| 108 | /// the left paragraph's list nodes under `lists`, in order. | ||
| 109 | pub(crate) fn changes_as( | ||
| 110 | &self, | ||
| 111 | active: &ActivePage<'_>, | ||
| 112 | paragraph_id: ExGuid, | ||
| 113 | right_text: ExGuid, | ||
| 114 | list_ids: &[ExGuid], | ||
| 115 | ) -> Result<Changes, Error> { | ||
| 88 | let [_] = active.pages.as_slice() else { | 116 | let [_] = active.pages.as_slice() else { |
| 89 | return Err(invalid("Splitting requires a single active page")); | 117 | return Err(invalid("Splitting requires a single active page")); |
| 90 | }; | 118 | }; |
| ... | @@ -136,10 +164,32 @@ impl ParagraphSplit { | ... | @@ -136,10 +164,32 @@ impl ParagraphSplit { |
| 136 | } | 164 | } |
| 137 | let raw = &active.live.revision; | 165 | let raw = &active.live.revision; |
| 138 | let (text, runs) = ordinary_text(view, raw, self.text)?; | 166 | let (text, runs) = ordinary_text(view, raw, self.text)?; |
| 167 | let resolved = view.text_runs(self.text)?; | ||
| 168 | let link = |at: usize| { | ||
| 169 | resolved | ||
| 170 | .get(at) | ||
| 171 | .is_some_and(|run| run.format.hyperlink == Some(true)) | ||
| 172 | }; | ||
| 173 | if let Some(after) = runs | ||
| 174 | .iter() | ||
| 175 | .position(|run| run.start < self.offset && self.offset < run.end) | ||
| 176 | { | ||
| 177 | if link(after) { | ||
| 178 | return Err(invalid("A split cannot divide a hyperlink")); | ||
| 179 | } | ||
| 180 | } else if let Some(after) = runs | ||
| 181 | .iter() | ||
| 182 | .position(|run| run.start == self.offset && self.offset > 0) | ||
| 183 | && link(after - 1) | ||
| 184 | && link(after) | ||
| 185 | && !resolved[after].text.starts_with('\u{fddf}') | ||
| 186 | { | ||
| 187 | return Err(invalid("A split cannot divide a hyperlink")); | ||
| 188 | } | ||
| 139 | let node = &view.nodes[&self.text]; | 189 | let node = &view.nodes[&self.text]; |
| 140 | let length = u32::try_from(text.encode_utf16().count()) | 190 | let length = u32::try_from(text.encode_utf16().count()) |
| 141 | .map_err(|_| invalid("Paragraph exceeds the UTF-16 offset range"))?; | 191 | .map_err(|_| invalid("Paragraph exceeds the UTF-16 offset range"))?; |
| 142 | if self.offset > length || lists.len() > 251 { | 192 | if self.offset > length || lists.len() > 251 || lists.len() > list_ids.len() { |
| 143 | return Err(invalid( | 193 | return Err(invalid( |
| 144 | "Choose a position within the paragraph and at most 251 list levels", | 194 | "Choose a position within the paragraph and at most 251 list levels", |
| 145 | )); | 195 | )); |
| ... | @@ -153,9 +203,14 @@ impl ParagraphSplit { | ... | @@ -153,9 +203,14 @@ impl ParagraphSplit { |
| 153 | n: 4, | 203 | n: 4, |
| 154 | }; | 204 | }; |
| 155 | let typing = runs.last().and_then(|run| run.format).unwrap_or(normal_id); | 205 | let typing = runs.last().and_then(|run| run.format).unwrap_or(normal_id); |
| 206 | // Enter at the start leaves the emptied upper paragraph the first run's style, as | ||
| 207 | // OneNote does (`evidence/structural-edits/xml/c2s-*`). | ||
| 208 | let leading = runs.first().and_then(|run| run.format); | ||
| 156 | let modified = current_timestamps()?.0.to_le_bytes(); | 209 | let modified = current_timestamps()?.0.to_le_bytes(); |
| 157 | let mut changed = BTreeMap::new(); | 210 | let mut changed = BTreeMap::new(); |
| 158 | if (self.offset == 0 && !text.is_empty()) || runs.iter().any(|run| run.format.is_none()) { | 211 | if (self.offset == 0 && !text.is_empty() && leading.is_none()) |
| 212 | || runs.iter().any(|run| run.format.is_none()) | ||
| 213 | { | ||
| 159 | changed.insert( | 214 | changed.insert( |
| 160 | normal_id, | 215 | normal_id, |
| 161 | PropertyObject { | 216 | PropertyObject { |
| ... | @@ -169,15 +224,22 @@ impl ParagraphSplit { | ... | @@ -169,15 +224,22 @@ impl ParagraphSplit { |
| 169 | &raw.objects[&self.text], | 224 | &raw.objects[&self.text], |
| 170 | node, | 225 | node, |
| 171 | 0..self.offset, | 226 | 0..self.offset, |
| 172 | if text.is_empty() { typing } else { normal_id }, | 227 | if text.is_empty() { |
| 228 | typing | ||
| 229 | } else if self.offset == 0 { | ||
| 230 | leading.unwrap_or(normal_id) | ||
| 231 | } else { | ||
| 232 | normal_id | ||
| 233 | }, | ||
| 173 | )?; | 234 | )?; |
| 174 | let mut suffix = fragment(&raw.objects[&self.text], node, self.offset..length, typing)?; | 235 | let mut suffix = fragment(&raw.objects[&self.text], node, self.offset..length, typing)?; |
| 175 | prefix.set(&[(0x14001d7a, &modified)])?; | 236 | prefix.set(&[(0x14001d7a, &modified)])?; |
| 176 | suffix.remove(&[0x40003489])?; | 237 | // Note tags and the recording link stay with the original text. |
| 238 | suffix.remove(&[0x40003489, 0x1c001c98, 0x14001c99])?; | ||
| 177 | suffix.set(&[(0x14001d7a, &modified)])?; | 239 | suffix.set(&[(0x14001d7a, &modified)])?; |
| 178 | suffix.reference(self.text_object())?; | 240 | suffix.reference(right_text)?; |
| 179 | changed.insert(self.text, prefix); | 241 | changed.insert(self.text, prefix); |
| 180 | changed.insert(self.text_object(), suffix); | 242 | changed.insert(right_text, suffix); |
| 181 | changed.insert( | 243 | changed.insert( |
| 182 | author_id, | 244 | author_id, |
| 183 | PropertyObject { | 245 | PropertyObject { |
| ... | @@ -187,7 +249,8 @@ impl ParagraphSplit { | ... | @@ -187,7 +249,8 @@ impl ParagraphSplit { |
| 187 | }, | 249 | }, |
| 188 | ); | 250 | ); |
| 189 | let mut right = PropertyObject::from_object(&raw.objects[paragraph])?; | 251 | let mut right = PropertyObject::from_object(&raw.objects[paragraph])?; |
| 190 | let content = right.reference(self.text_object())?; | 252 | right.reference(paragraph_id)?; |
| 253 | let content = right.reference(right_text)?; | ||
| 191 | let author = right.reference(author_id)?; | 254 | let author = right.reference(author_id)?; |
| 192 | right.set(&[ | 255 | right.set(&[ |
| 193 | (0x24001c1f, &content), | 256 | (0x24001c1f, &content), |
| ... | @@ -202,10 +265,7 @@ impl ParagraphSplit { | ... | @@ -202,10 +265,7 @@ impl ParagraphSplit { |
| 202 | if !matches!(view.nodes[old].kind, Kind::List { .. }) { | 265 | if !matches!(view.nodes[old].kind, Kind::List { .. }) { |
| 203 | return Err(invalid("The paragraph list is unavailable")); | 266 | return Err(invalid("The paragraph list is unavailable")); |
| 204 | } | 267 | } |
| 205 | let id = ExGuid { | 268 | let id = list_ids[i]; |
| 206 | guid: self.guid, | ||
| 207 | n: 5 + u32::try_from(i).unwrap(), | ||
| 208 | }; | ||
| 209 | let mut list = PropertyObject::from_object(&raw.objects[old])?; | 269 | let mut list = PropertyObject::from_object(&raw.objects[old])?; |
| 210 | list.remove(&[0x14001cb7])?; | 270 | list.remove(&[0x14001cb7])?; |
| 211 | list.reference(id)?; | 271 | list.reference(id)?; |
| ... | @@ -214,7 +274,7 @@ impl ParagraphSplit { | ... | @@ -214,7 +274,7 @@ impl ParagraphSplit { |
| 214 | } | 274 | } |
| 215 | right.set(&[(0x24001c26, &references)])?; | 275 | right.set(&[(0x24001c26, &references)])?; |
| 216 | } | 276 | } |
| 217 | changed.insert(self.object(), right); | 277 | changed.insert(paragraph_id, right); |
| 218 | let mut original = PropertyObject::from_object(&raw.objects[paragraph])?; | 278 | let mut original = PropertyObject::from_object(&raw.objects[paragraph])?; |
| 219 | original.remove(&[0x24001c20])?; | 279 | original.remove(&[0x24001c20])?; |
| 220 | let author = original.reference(author_id)?; | 280 | let author = original.reference(author_id)?; |
| ... | @@ -225,19 +285,11 @@ impl ParagraphSplit { | ... | @@ -225,19 +285,11 @@ impl ParagraphSplit { |
| 225 | for child in &view.nodes[parent].children { | 285 | for child in &view.nodes[parent].children { |
| 226 | children.extend_from_slice(&parent_object.reference(*child)?); | 286 | children.extend_from_slice(&parent_object.reference(*child)?); |
| 227 | if child == paragraph { | 287 | if child == paragraph { |
| 228 | children.extend_from_slice(&parent_object.reference(self.object())?); | 288 | children.extend_from_slice(&parent_object.reference(paragraph_id)?); |
| 229 | } | 289 | } |
| 230 | } | 290 | } |
| 231 | parent_object.set(&[(0x24001c20, &children)])?; | 291 | parent_object.set(&[(0x24001c20, &children)])?; |
| 232 | changed.insert(*parent, parent_object); | 292 | changed.insert(*parent, parent_object); |
| 233 | if changed | ||
| 234 | .keys() | ||
| 235 | .any(|id| id.guid == self.guid && raw.objects.contains_key(id)) | ||
| 236 | { | ||
| 237 | return Err(invalid( | ||
| 238 | "A split identity already exists; reconcile the existing edit", | ||
| 239 | )); | ||
| 240 | } | ||
| 241 | for id in ancestors { | 293 | for id in ancestors { |
| 242 | let object = match changed.entry(id) { | 294 | let object = match changed.entry(id) { |
| 243 | std::collections::btree_map::Entry::Occupied(entry) => entry.into_mut(), | 295 | std::collections::btree_map::Entry::Occupied(entry) => entry.into_mut(), |
| ... | @@ -311,6 +363,9 @@ fn fragment( | ... | @@ -311,6 +363,9 @@ fn fragment( |
| 311 | Ok(object) | 363 | Ok(object) |
| 312 | } | 364 | } |
| 313 | 365 | ||
| 366 | /// A text object's text and runs, unless it holds what a split or join cannot divide or | ||
| 367 | /// merge: generated title text, equations, embedded objects or per-run data. Hyperlink | ||
| 368 | /// fields are ordinary runs; a recording link belongs to the text object. | ||
| 314 | fn ordinary_text<'a>( | 369 | fn ordinary_text<'a>( |
| 315 | view: &'a crate::document::Revision<'_>, | 370 | view: &'a crate::document::Revision<'_>, |
| 316 | raw: &crate::ResolvedRevision<'_>, | 371 | raw: &crate::ResolvedRevision<'_>, |
| ... | @@ -326,23 +381,15 @@ fn ordinary_text<'a>( | ... | @@ -326,23 +381,15 @@ fn ordinary_text<'a>( |
| 326 | else { | 381 | else { |
| 327 | return Err(invalid("Select ordinary paragraph text")); | 382 | return Err(invalid("Select ordinary paragraph text")); |
| 328 | }; | 383 | }; |
| 329 | if *boilerplate || !node.media_ids.is_empty() || node.media_time_ms.is_some() { | 384 | if *boilerplate { |
| 330 | return Err(invalid( | 385 | return Err(invalid("Generated text cannot be split or joined")); |
| 331 | "Generated or recording-linked text cannot be split or joined", | ||
| 332 | )); | ||
| 333 | } | 386 | } |
| 334 | for run in view.text_runs(id)? { | 387 | for run in view.text_runs(id)? { |
| 335 | if [ | 388 | if [run.format.math, run.format.embedded_object].contains(&Some(true)) |
| 336 | run.format.hidden, | 389 | || run.text.contains('\u{fffc}') |
| 337 | run.format.hyperlink, | ||
| 338 | run.format.math, | ||
| 339 | run.format.embedded_object, | ||
| 340 | ] | ||
| 341 | .contains(&Some(true)) | ||
| 342 | || run.text.contains(['\u{fffc}', '\u{fddf}']) | ||
| 343 | { | 390 | { |
| 344 | return Err(invalid( | 391 | return Err(invalid( |
| 345 | "This paragraph contains a field or embedded object that cannot be split or joined", | 392 | "This paragraph contains an equation or embedded object that cannot be split or joined", |
| 346 | )); | 393 | )); |
| 347 | } | 394 | } |
| 348 | } | 395 | } |
crates/onestore/src/section.rs created+1235| ... | @@ -0,0 +1,1235 @@ | ||
| 1 | //! A section file kept parsed across edits. Edits apply to its object spaces in memory and | ||
| 2 | //! seal into one transaction appending one revision per changed space. | ||
| 3 | |||
| 4 | use crate::{ | ||
| 5 | Chunk, Error, ExGuid, FileType, ObjectData, Reference, ResolvedRevision, RevisionIndex, Stamp, | ||
| 6 | Store, Transaction, | ||
| 7 | active::{ActivePage, Changes, Files}, | ||
| 8 | document::Revision, | ||
| 9 | page::Page, | ||
| 10 | store::StoreState, | ||
| 11 | write::{ | ||
| 12 | Appending, LiveRevision, Sealing, Written, chain_depth, check_transaction, declared, | ||
| 13 | replacement, unchanged, | ||
| 14 | }, | ||
| 15 | }; | ||
| 16 | use bumpalo::Bump; | ||
| 17 | use std::collections::{BTreeMap, BTreeSet}; | ||
| 18 | |||
| 19 | type Result<T> = std::result::Result<T, Error>; | ||
| 20 | |||
| 21 | /// Holds the bytes a `Section` reads: the image it opened and each transaction it sealed. | ||
| 22 | #[derive(Default)] | ||
| 23 | pub struct Arena(Bump); | ||
| 24 | |||
| 25 | /// A section file as its image and the transactions sealed on it leave it. | ||
| 26 | pub struct Section<'a> { | ||
| 27 | arena: &'a Arena, | ||
| 28 | /// The opened image, then the bytes each sealed transaction appended, at their offsets. | ||
| 29 | segments: Vec<(u64, &'a [u8])>, | ||
| 30 | /// The in-place writes of the sealed transactions, in order. | ||
| 31 | patches: Vec<(u64, Vec<u8>)>, | ||
| 32 | state: StoreState, | ||
| 33 | /// Payloads the opened image embeds, by identity. | ||
| 34 | files: &'a [([u8; 16], Chunk)], | ||
| 35 | /// Payload identities the file-data store declares. | ||
| 36 | declared: BTreeSet<[u8; 16]>, | ||
| 37 | spaces: BTreeMap<ExGuid, Space<'a>>, | ||
| 38 | /// Payloads applied changes embed, in order, until sealed. | ||
| 39 | payloads: Vec<([u8; 16], &'a [u8])>, | ||
| 40 | /// Whether an operation failed after it began changing state; the section must be | ||
| 41 | /// reopened. | ||
| 42 | broken: bool, | ||
| 43 | /// The root object space, which lists the pages. | ||
| 44 | root: ExGuid, | ||
| 45 | /// While an edit applies, the spaces it changed as they were before it. | ||
| 46 | undo: Option<Undo<'a>>, | ||
| 47 | } | ||
| 48 | |||
| 49 | #[derive(Default)] | ||
| 50 | struct Undo<'a> { | ||
| 51 | /// `None` for a space the edit created. | ||
| 52 | spaces: BTreeMap<ExGuid, Option<Space<'a>>>, | ||
| 53 | payloads: usize, | ||
| 54 | } | ||
| 55 | |||
| 56 | #[derive(Clone)] | ||
| 57 | struct Space<'a> { | ||
| 58 | /// The active revision the file stores; none before a new space's first seal. | ||
| 59 | rid: Option<ExGuid>, | ||
| 60 | state: SpaceState<'a>, | ||
| 61 | } | ||
| 62 | |||
| 63 | #[derive(Clone)] | ||
| 64 | enum SpaceState<'a> { | ||
| 65 | /// As the opened image stores it, until first edited. | ||
| 66 | Stored { | ||
| 67 | revision: ResolvedRevision<'a>, | ||
| 68 | depth: usize, | ||
| 69 | }, | ||
| 70 | Open(Box<Open<'a>>), | ||
| 71 | } | ||
| 72 | |||
| 73 | #[derive(Clone)] | ||
| 74 | struct Open<'a> { | ||
| 75 | /// The revision the file stores. | ||
| 76 | file: LiveRevision<'a>, | ||
| 77 | /// `file` with the changes applied since the last seal. | ||
| 78 | page: ActivePage<'a>, | ||
| 79 | /// Objects whose content, reachability or count changed since the last seal. | ||
| 80 | pending: BTreeSet<ExGuid>, | ||
| 81 | } | ||
| 82 | |||
| 83 | impl<'a> Section<'a> { | ||
| 84 | /// Parses and fully validates a section image. | ||
| 85 | pub fn open(arena: &'a Arena, image: Vec<u8>) -> Result<Self> { | ||
| 86 | let bytes: &'a [u8] = arena.0.alloc_slice_copy(&image); | ||
| 87 | drop(image); | ||
| 88 | let store = Store::parse(bytes)?; | ||
| 89 | if let Some(offset) = store.checksum_mismatches.first() { | ||
| 90 | return Err(Error { | ||
| 91 | offset: *offset, | ||
| 92 | message: "Cannot write a file with transaction checksum damage", | ||
| 93 | }); | ||
| 94 | } | ||
| 95 | if store.header.file_type != FileType::Section { | ||
| 96 | return Err(Error { | ||
| 97 | offset: 0, | ||
| 98 | message: "Choose a section file", | ||
| 99 | }); | ||
| 100 | } | ||
| 101 | let index = RevisionIndex::parse(&store)?; | ||
| 102 | index.validate_current()?; | ||
| 103 | let mut spaces = BTreeMap::new(); | ||
| 104 | for (id, space) in &index.spaces { | ||
| 105 | let Some(rid) = space.labels.get(&(ExGuid::default(), 1)).copied() else { | ||
| 106 | continue; | ||
| 107 | }; | ||
| 108 | let resolved = index.resolve(*id, rid)?; | ||
| 109 | let revision = ResolvedRevision { | ||
| 110 | roots: resolved.roots, | ||
| 111 | objects: resolved | ||
| 112 | .objects | ||
| 113 | .into_iter() | ||
| 114 | .map(|(id, object)| { | ||
| 115 | // The parse borrows `bytes` only as long as `store`; its slices lie | ||
| 116 | // in `bytes`, which the arena keeps. | ||
| 117 | let data = match object.data { | ||
| 118 | ObjectData::Properties(data) => { | ||
| 119 | ObjectData::Properties(within(bytes, data)) | ||
| 120 | } | ||
| 121 | ObjectData::Encrypted(data) => { | ||
| 122 | ObjectData::Encrypted(within(bytes, data)) | ||
| 123 | } | ||
| 124 | ObjectData::File { | ||
| 125 | reference, | ||
| 126 | extension, | ||
| 127 | } => ObjectData::File { | ||
| 128 | reference: within(bytes, reference), | ||
| 129 | extension: within(bytes, extension), | ||
| 130 | }, | ||
| 131 | }; | ||
| 132 | let object = crate::Object { | ||
| 133 | jcid: object.jcid, | ||
| 134 | reference_count: object.reference_count, | ||
| 135 | data, | ||
| 136 | global_ids: object.global_ids, | ||
| 137 | }; | ||
| 138 | (id, object) | ||
| 139 | }) | ||
| 140 | .collect(), | ||
| 141 | }; | ||
| 142 | spaces.insert( | ||
| 143 | *id, | ||
| 144 | Space { | ||
| 145 | rid: Some(rid), | ||
| 146 | state: SpaceState::Stored { | ||
| 147 | revision, | ||
| 148 | depth: chain_depth(&index, *id, rid), | ||
| 149 | }, | ||
| 150 | }, | ||
| 151 | ); | ||
| 152 | } | ||
| 153 | let mut files = Vec::new(); | ||
| 154 | for node in store.lists.values().flat_map(|list| &list.nodes) { | ||
| 155 | if node.id != 0x94 { | ||
| 156 | continue; | ||
| 157 | } | ||
| 158 | let (Some(guid), Some(Reference::Data(chunk))) = | ||
| 159 | (node.payload.first_chunk(), node.reference) | ||
| 160 | else { | ||
| 161 | return Err(Error { | ||
| 162 | offset: node.offset, | ||
| 163 | message: "File-data object lacks a data reference", | ||
| 164 | }); | ||
| 165 | }; | ||
| 166 | files.push((*guid, chunk)); | ||
| 167 | } | ||
| 168 | files.sort_unstable_by_key(|(guid, _)| *guid); | ||
| 169 | Ok(Self { | ||
| 170 | arena, | ||
| 171 | segments: vec![(0, bytes)], | ||
| 172 | patches: Vec::new(), | ||
| 173 | state: store.state()?, | ||
| 174 | declared: files.iter().map(|(guid, _)| *guid).collect(), | ||
| 175 | files: arena.0.alloc_slice_copy(&files), | ||
| 176 | spaces, | ||
| 177 | payloads: Vec::new(), | ||
| 178 | broken: false, | ||
| 179 | root: index.root, | ||
| 180 | undo: None, | ||
| 181 | }) | ||
| 182 | } | ||
| 183 | |||
| 184 | /// The header and length of the image this section's sealed transactions leave. | ||
| 185 | pub fn stamp(&self) -> &Stamp { | ||
| 186 | &self.state.stamp | ||
| 187 | } | ||
| 188 | |||
| 189 | /// The image this section's sealed transactions leave; for tests and seeding caches. | ||
| 190 | pub fn image(&self) -> Vec<u8> { | ||
| 191 | let mut image = self | ||
| 192 | .segments | ||
| 193 | .iter() | ||
| 194 | .flat_map(|(_, bytes)| *bytes) | ||
| 195 | .copied() | ||
| 196 | .collect::<Vec<_>>(); | ||
| 197 | for (offset, bytes) in &self.patches { | ||
| 198 | let offset = *offset as usize; | ||
| 199 | image[offset..offset + bytes.len()].copy_from_slice(bytes); | ||
| 200 | } | ||
| 201 | image[..1024].copy_from_slice(&self.state.stamp.header); | ||
| 202 | image | ||
| 203 | } | ||
| 204 | |||
| 205 | /// The page an object space holds, with the changes applied since the last seal. | ||
| 206 | pub fn page(&self, space: ExGuid) -> Result<Page> { | ||
| 207 | let stored = self.spaces.get(&space).ok_or(Error { | ||
| 208 | offset: 0, | ||
| 209 | message: "Object space has no active default revision", | ||
| 210 | })?; | ||
| 211 | match &stored.state { | ||
| 212 | SpaceState::Open(open) => one_page(&open.page.view, &open.page.pages), | ||
| 213 | SpaceState::Stored { revision, .. } => { | ||
| 214 | let files = self.files(); | ||
| 215 | let (view, _) = | ||
| 216 | Revision::parse(space, revision, FileType::Section, &mut |guid| files(guid))?; | ||
| 217 | one_page(&view, &crate::active::manifest_pages(&view)) | ||
| 218 | } | ||
| 219 | } | ||
| 220 | } | ||
| 221 | |||
| 222 | /// Page spaces in section order with the title and outline level (1 at the top) the | ||
| 223 | /// page list shows, read from each page's metadata alone. | ||
| 224 | pub fn pages(&mut self) -> Result<Vec<(ExGuid, String, u32)>> { | ||
| 225 | let root = self.root; | ||
| 226 | let view = &self.active(root)?.view; | ||
| 227 | let section = view.roots.get(&1).and_then(|id| view.nodes.get(id)).ok_or(Error { | ||
| 228 | offset: 0, | ||
| 229 | message: "Section root is unavailable", | ||
| 230 | })?; | ||
| 231 | let spaces: Vec<ExGuid> = section | ||
| 232 | .children | ||
| 233 | .iter() | ||
| 234 | .filter_map(|series| view.nodes.get(series)) | ||
| 235 | .flat_map(|series| series.spaces.clone()) | ||
| 236 | .collect(); | ||
| 237 | let element = |object: &crate::Object<'a>| { | ||
| 238 | crate::document::Element::parse_with(object, FileType::Section, &mut |_| { | ||
| 239 | Err(Error { | ||
| 240 | offset: 0, | ||
| 241 | message: "Page metadata holds no payload", | ||
| 242 | }) | ||
| 243 | }) | ||
| 244 | }; | ||
| 245 | let mut pages = Vec::new(); | ||
| 246 | for space in spaces { | ||
| 247 | let revision = self.revision(space)?; | ||
| 248 | let (mut title, mut level) = (None, 1); | ||
| 249 | if let Some(metadata) = revision.roots.get(&2).and_then(|id| revision.objects.get(id)) | ||
| 250 | && let crate::document::Kind::Metadata { | ||
| 251 | title: stored, | ||
| 252 | level: stored_level, | ||
| 253 | } = element(metadata)?.kind | ||
| 254 | { | ||
| 255 | title = stored; | ||
| 256 | level = stored_level.unwrap_or(1); | ||
| 257 | } | ||
| 258 | if title.is_none() { | ||
| 259 | let manifest = revision.roots.get(&1).and_then(|id| revision.objects.get(id)); | ||
| 260 | if let Some(manifest) = manifest | ||
| 261 | && let Some(page) = element(manifest)? | ||
| 262 | .content | ||
| 263 | .first() | ||
| 264 | .and_then(|id| revision.objects.get(id)) | ||
| 265 | && let crate::document::Kind::Page { | ||
| 266 | alternate_title, .. | ||
| 267 | } = element(page)?.kind | ||
| 268 | { | ||
| 269 | title = alternate_title; | ||
| 270 | } | ||
| 271 | } | ||
| 272 | pages.push((space, title.unwrap_or_default(), level)); | ||
| 273 | } | ||
| 274 | Ok(pages) | ||
| 275 | } | ||
| 276 | |||
| 277 | /// Reads a payload of the opened image by identity. | ||
| 278 | fn files(&self) -> Files<'a> { | ||
| 279 | let (bytes, files) = (self.segments[0].1, self.files); | ||
| 280 | std::rc::Rc::new(move |guid| { | ||
| 281 | let start = files.partition_point(|(id, _)| *id < guid); | ||
| 282 | match files[start..] | ||
| 283 | .iter() | ||
| 284 | .take_while(|(id, _)| *id == guid) | ||
| 285 | .count() | ||
| 286 | { | ||
| 287 | 0 => Err(Error { | ||
| 288 | offset: 0, | ||
| 289 | message: "File-data object is not declared", | ||
| 290 | }), | ||
| 291 | 1 => { | ||
| 292 | let chunk = files[start].1; | ||
| 293 | let (offset, length) = (chunk.offset as usize, chunk.length as usize); | ||
| 294 | let blob = bytes.get(offset..offset + length).ok_or(Error { | ||
| 295 | offset, | ||
| 296 | message: "Chunk extends outside the data area", | ||
| 297 | })?; | ||
| 298 | crate::files::payload(blob, offset) | ||
| 299 | } | ||
| 300 | _ => Err(Error { | ||
| 301 | offset: 0, | ||
| 302 | message: "Duplicate file-data identity", | ||
| 303 | }), | ||
| 304 | } | ||
| 305 | }) | ||
| 306 | } | ||
| 307 | |||
| 308 | fn usable(&self) -> Result<()> { | ||
| 309 | if self.broken { | ||
| 310 | return Err(Error { | ||
| 311 | offset: 0, | ||
| 312 | message: "A failed edit left the section to be reopened", | ||
| 313 | }); | ||
| 314 | } | ||
| 315 | Ok(()) | ||
| 316 | } | ||
| 317 | |||
| 318 | /// The editable state of an object space, opened on first use. | ||
| 319 | pub(crate) fn active(&mut self, space: ExGuid) -> Result<&ActivePage<'a>> { | ||
| 320 | Ok(&self.editable(space)?.page) | ||
| 321 | } | ||
| 322 | |||
| 323 | fn editable(&mut self, id: ExGuid) -> Result<&mut Open<'a>> { | ||
| 324 | let files = self.files(); | ||
| 325 | let space = self.spaces.get_mut(&id).ok_or(Error { | ||
| 326 | offset: 0, | ||
| 327 | message: "Object space has no active default revision", | ||
| 328 | })?; | ||
| 329 | if let SpaceState::Stored { revision, depth } = &space.state { | ||
| 330 | let file = LiveRevision::new(revision.clone(), *depth)?; | ||
| 331 | let page = ActivePage::open(id, file.clone(), FileType::Section, files)?; | ||
| 332 | space.state = SpaceState::Open(Box::new(Open { | ||
| 333 | file, | ||
| 334 | page, | ||
| 335 | pending: BTreeSet::new(), | ||
| 336 | })); | ||
| 337 | } | ||
| 338 | let SpaceState::Open(open) = &mut space.state else { | ||
| 339 | unreachable!() | ||
| 340 | }; | ||
| 341 | Ok(open) | ||
| 342 | } | ||
| 343 | |||
| 344 | /// Stores a writer's objects and payloads in a space until the next seal; false when | ||
| 345 | /// it stores nothing. An error leaves the section to be reopened. | ||
| 346 | pub(crate) fn apply_changes( | ||
| 347 | &mut self, | ||
| 348 | space: ExGuid, | ||
| 349 | payloads: &[([u8; 16], &[u8])], | ||
| 350 | changes: Changes, | ||
| 351 | ) -> Result<bool> { | ||
| 352 | self.usable()?; | ||
| 353 | if let Some(undo) = &mut self.undo | ||
| 354 | && !undo.spaces.contains_key(&space) | ||
| 355 | { | ||
| 356 | undo.spaces.insert(space, self.spaces.get(&space).cloned()); | ||
| 357 | } | ||
| 358 | let arena = &self.arena.0; | ||
| 359 | self.broken = true; | ||
| 360 | let open = self.editable(space)?; | ||
| 361 | let embedded = open.page.payloads.len(); | ||
| 362 | let Some(touched) = open.page.store(arena, payloads, changes)? else { | ||
| 363 | self.broken = false; | ||
| 364 | return Ok(false); | ||
| 365 | }; | ||
| 366 | open.pending.extend(touched); | ||
| 367 | let embedded = open.page.payloads[embedded..].to_vec(); | ||
| 368 | self.payloads.extend(embedded); | ||
| 369 | self.broken = false; | ||
| 370 | Ok(true) | ||
| 371 | } | ||
| 372 | |||
| 373 | /// Creates an object space holding `changes` under `roots` until the next seal, which | ||
| 374 | /// declares it. An error after validation leaves the section to be reopened. | ||
| 375 | pub(crate) fn create_space( | ||
| 376 | &mut self, | ||
| 377 | space: ExGuid, | ||
| 378 | roots: BTreeMap<u32, ExGuid>, | ||
| 379 | changes: Changes, | ||
| 380 | ) -> Result<()> { | ||
| 381 | self.usable()?; | ||
| 382 | if space.guid == [0; 16] || self.spaces.contains_key(&space) { | ||
| 383 | return Err(Error { | ||
| 384 | offset: 0, | ||
| 385 | message: "Choose a new object-space identity in a section file", | ||
| 386 | }); | ||
| 387 | } | ||
| 388 | if roots.is_empty() || changes.is_empty() { | ||
| 389 | return Err(Error { | ||
| 390 | offset: 0, | ||
| 391 | message: "New object space needs roots and objects", | ||
| 392 | }); | ||
| 393 | } | ||
| 394 | let file = LiveRevision::new( | ||
| 395 | ResolvedRevision { | ||
| 396 | roots, | ||
| 397 | objects: BTreeMap::new(), | ||
| 398 | }, | ||
| 399 | 0, | ||
| 400 | )?; | ||
| 401 | let mut work = file.clone(); | ||
| 402 | let changes = work.prepare(changes, true)?; | ||
| 403 | let mut objects = Vec::new(); | ||
| 404 | for (id, change) in changes { | ||
| 405 | let bytes = self.arena.0.alloc_slice_copy(&change.bytes); | ||
| 406 | objects.push((id, declared(change.jcid, bytes, change.global_ids)?)); | ||
| 407 | } | ||
| 408 | if let Some(undo) = &mut self.undo { | ||
| 409 | undo.spaces.entry(space).or_insert(None); | ||
| 410 | } | ||
| 411 | self.broken = true; | ||
| 412 | let pending = work.commit(objects)?.touched; | ||
| 413 | let all: Vec<ExGuid> = work.revision.objects.keys().copied().collect(); | ||
| 414 | work.settle(all)?; | ||
| 415 | let page = ActivePage::open(space, work, FileType::Section, self.files())?; | ||
| 416 | self.spaces.insert( | ||
| 417 | space, | ||
| 418 | Space { | ||
| 419 | rid: None, | ||
| 420 | state: SpaceState::Open(Box::new(Open { | ||
| 421 | file, | ||
| 422 | page, | ||
| 423 | pending, | ||
| 424 | })), | ||
| 425 | }, | ||
| 426 | ); | ||
| 427 | self.broken = false; | ||
| 428 | Ok(()) | ||
| 429 | } | ||
| 430 | |||
| 431 | /// The root object space, which lists the pages. | ||
| 432 | pub(crate) fn root(&self) -> ExGuid { | ||
| 433 | self.root | ||
| 434 | } | ||
| 435 | |||
| 436 | /// The stored revision of a space, as edits since the last seal leave it. | ||
| 437 | pub(crate) fn revision(&self, space: ExGuid) -> Result<&ResolvedRevision<'a>> { | ||
| 438 | match self.spaces.get(&space).map(|space| &space.state) { | ||
| 439 | Some(SpaceState::Stored { revision, .. }) => Ok(revision), | ||
| 440 | Some(SpaceState::Open(open)) => Ok(&open.page.live.revision), | ||
| 441 | None => Err(Error { | ||
| 442 | offset: 0, | ||
| 443 | message: "Object space has no active default revision", | ||
| 444 | }), | ||
| 445 | } | ||
| 446 | } | ||
| 447 | |||
| 448 | /// Runs `f`, returning the section to its state before it when `f` fails; `undo` | ||
| 449 | /// saves each space before `f` first changes it, which costs a copy of that space. | ||
| 450 | pub(crate) fn atomically<T, E>( | ||
| 451 | &mut self, | ||
| 452 | undo: bool, | ||
| 453 | f: impl FnOnce(&mut Self) -> std::result::Result<T, E>, | ||
| 454 | ) -> std::result::Result<T, E> { | ||
| 455 | if undo { | ||
| 456 | self.undo = Some(Undo { | ||
| 457 | spaces: BTreeMap::new(), | ||
| 458 | payloads: self.payloads.len(), | ||
| 459 | }); | ||
| 460 | } | ||
| 461 | let result = f(self); | ||
| 462 | if let Some(undo) = self.undo.take() | ||
| 463 | && result.is_err() | ||
| 464 | { | ||
| 465 | for (id, space) in undo.spaces { | ||
| 466 | match space { | ||
| 467 | Some(space) => self.spaces.insert(id, space), | ||
| 468 | None => self.spaces.remove(&id), | ||
| 469 | }; | ||
| 470 | } | ||
| 471 | self.payloads.truncate(undo.payloads); | ||
| 472 | self.broken = false; | ||
| 473 | } | ||
| 474 | result | ||
| 475 | } | ||
| 476 | |||
| 477 | /// Whether a failed operation left the section to be reopened. | ||
| 478 | pub(crate) fn broken(&self) -> bool { | ||
| 479 | self.broken | ||
| 480 | } | ||
| 481 | |||
| 482 | /// Appends one revision per space changed since the last seal, and the payloads they | ||
| 483 | /// embed, as one transaction on `stamp`; none when nothing changed. An error leaves the | ||
| 484 | /// section to be reopened. | ||
| 485 | pub fn seal(&mut self) -> Result<Option<Transaction>> { | ||
| 486 | self.usable()?; | ||
| 487 | self.broken = true; | ||
| 488 | let arena = &self.arena.0; | ||
| 489 | let mut appending = Appending::new(self.state.clone()); | ||
| 490 | let mut sealed = Vec::new(); | ||
| 491 | for (id, space) in &mut self.spaces { | ||
| 492 | let SpaceState::Open(open) = &mut space.state else { | ||
| 493 | continue; | ||
| 494 | }; | ||
| 495 | if open.pending.is_empty() { | ||
| 496 | continue; | ||
| 497 | } | ||
| 498 | let (work, file) = (&open.page.live, &mut open.file); | ||
| 499 | let mut replacements = BTreeMap::new(); | ||
| 500 | for object_id in &open.pending { | ||
| 501 | if !work.is_reachable(*object_id) { | ||
| 502 | continue; | ||
| 503 | } | ||
| 504 | let object = &work.revision.objects[object_id]; | ||
| 505 | if let Some(stored) = file.revision.objects.get(object_id) | ||
| 506 | && unchanged(stored, object)? | ||
| 507 | { | ||
| 508 | continue; | ||
| 509 | } | ||
| 510 | replacements.insert(*object_id, replacement(*object_id, object)?); | ||
| 511 | } | ||
| 512 | let replacements = file.prepare(replacements, true)?; | ||
| 513 | if replacements.is_empty() { | ||
| 514 | sealed.push((*id, None)); | ||
| 515 | continue; | ||
| 516 | } | ||
| 517 | let replaced: BTreeSet<ExGuid> = replacements.keys().copied().collect(); | ||
| 518 | let created = replaced | ||
| 519 | .iter() | ||
| 520 | .filter(|id| !file.revision.objects.contains_key(id)) | ||
| 521 | .copied() | ||
| 522 | .collect(); | ||
| 523 | let mut objects = Vec::new(); | ||
| 524 | for (object_id, change) in replacements { | ||
| 525 | // The page holds these bytes already unless preparing remapped them. | ||
| 526 | let bytes = match work.revision.objects.get(&object_id).map(|o| o.data) { | ||
| 527 | Some(ObjectData::Properties(bytes)) if bytes == change.bytes => bytes, | ||
| 528 | _ => arena.alloc_slice_copy(&change.bytes), | ||
| 529 | }; | ||
| 530 | objects.push((object_id, declared(change.jcid, bytes, change.global_ids)?)); | ||
| 531 | } | ||
| 532 | let commit = file.commit(objects)?; | ||
| 533 | let (rid, stored) = appending.revision( | ||
| 534 | &Sealing { | ||
| 535 | space: *id, | ||
| 536 | previous: space.rid, | ||
| 537 | new_space: space.rid.is_none(), | ||
| 538 | label: (ExGuid::default(), 1), | ||
| 539 | live: file, | ||
| 540 | commit: &commit, | ||
| 541 | replaced: &replaced, | ||
| 542 | created: &created, | ||
| 543 | }, | ||
| 544 | &self.segments, | ||
| 545 | None, | ||
| 546 | None, | ||
| 547 | )?; | ||
| 548 | if commit.checkpoint { | ||
| 549 | let all: Vec<ExGuid> = file.revision.objects.keys().copied().collect(); | ||
| 550 | file.settle(all)?; | ||
| 551 | } else { | ||
| 552 | file.settle(commit.changed.iter().copied())?; | ||
| 553 | } | ||
| 554 | sealed.push((*id, Some((rid, stored, commit)))); | ||
| 555 | } | ||
| 556 | let payloads: Vec<_> = self | ||
| 557 | .payloads | ||
| 558 | .iter() | ||
| 559 | .filter(|(guid, _)| !self.declared.contains(guid)) | ||
| 560 | .copied() | ||
| 561 | .collect(); | ||
| 562 | appending.payloads(&payloads, None)?; | ||
| 563 | #[cfg_attr(not(test), expect(unused_mut))] | ||
| 564 | let mut transaction = appending.finish()?; | ||
| 565 | #[cfg(test)] | ||
| 566 | if let (Some(tamper), Some((transaction, _))) = (tests::TAMPER.get(), &mut transaction) { | ||
| 567 | tamper(transaction); | ||
| 568 | } | ||
| 569 | if let Some((transaction, state)) = &transaction { | ||
| 570 | let mut written = Vec::new(); | ||
| 571 | for (id, revision) in &sealed { | ||
| 572 | let (Some((rid, _, commit)), Some(space)) = (revision, self.spaces.get(id)) else { | ||
| 573 | continue; | ||
| 574 | }; | ||
| 575 | let SpaceState::Open(open) = &space.state else { | ||
| 576 | unreachable!() | ||
| 577 | }; | ||
| 578 | written.push(Written { | ||
| 579 | space: *id, | ||
| 580 | rid: *rid, | ||
| 581 | previous: space.rid, | ||
| 582 | live: &open.file, | ||
| 583 | commit, | ||
| 584 | }); | ||
| 585 | } | ||
| 586 | check_transaction( | ||
| 587 | &self.state, | ||
| 588 | state, | ||
| 589 | transaction, | ||
| 590 | &self.segments, | ||
| 591 | &written, | ||
| 592 | &payloads, | ||
| 593 | )?; | ||
| 594 | let base = transaction.base.length; | ||
| 595 | let segment: &'a [u8] = arena.alloc_slice_copy(&transaction.append); | ||
| 596 | self.segments.push((base, segment)); | ||
| 597 | self.patches.extend(transaction.patches.iter().cloned()); | ||
| 598 | self.state = state.clone(); | ||
| 599 | self.declared.extend(payloads.iter().map(|(guid, _)| *guid)); | ||
| 600 | for (id, revision) in &sealed { | ||
| 601 | let Some((rid, stored, _)) = revision else { | ||
| 602 | continue; | ||
| 603 | }; | ||
| 604 | let space = self.spaces.get_mut(id).unwrap(); | ||
| 605 | space.rid = Some(*rid); | ||
| 606 | let SpaceState::Open(open) = &mut space.state else { | ||
| 607 | unreachable!() | ||
| 608 | }; | ||
| 609 | for (object_id, chunk) in stored { | ||
| 610 | let start = (chunk.offset - base) as usize; | ||
| 611 | open.file.revision.objects.get_mut(object_id).unwrap().data = | ||
| 612 | ObjectData::Properties(&segment[start..start + chunk.length as usize]); | ||
| 613 | } | ||
| 614 | } | ||
| 615 | } | ||
| 616 | for (id, revision) in sealed { | ||
| 617 | let SpaceState::Open(open) = &mut self.spaces.get_mut(&id).unwrap().state else { | ||
| 618 | unreachable!() | ||
| 619 | }; | ||
| 620 | let mut compare = std::mem::take(&mut open.pending); | ||
| 621 | if let Some((_, _, commit)) = revision { | ||
| 622 | if commit.checkpoint { | ||
| 623 | compare.extend(open.file.revision.objects.keys()); | ||
| 624 | } else { | ||
| 625 | compare.extend(commit.touched); | ||
| 626 | } | ||
| 627 | } | ||
| 628 | open.page.adopt(&open.file, compare)?; | ||
| 629 | } | ||
| 630 | self.payloads.clear(); | ||
| 631 | self.broken = false; | ||
| 632 | Ok(transaction.map(|(transaction, _)| transaction)) | ||
| 633 | } | ||
| 634 | |||
| 635 | /// Applies a transaction sealed on this section's stamp, as reopening the image it | ||
| 636 | /// leaves would; nothing may be applied since the last seal. | ||
| 637 | pub fn replay(&mut self, transaction: &Transaction) -> Result<()> { | ||
| 638 | self.usable()?; | ||
| 639 | if !self.payloads.is_empty() | ||
| 640 | || self.spaces.values().any( | ||
| 641 | |space| matches!(&space.state, SpaceState::Open(open) if !open.pending.is_empty()), | ||
| 642 | ) | ||
| 643 | { | ||
| 644 | return Err(Error { | ||
| 645 | offset: 0, | ||
| 646 | message: "Seal applied changes before replaying a transaction", | ||
| 647 | }); | ||
| 648 | } | ||
| 649 | let mut image = self.image(); | ||
| 650 | transaction.apply(&mut image)?; | ||
| 651 | *self = Self::open(self.arena, image)?; | ||
| 652 | Ok(()) | ||
| 653 | } | ||
| 654 | } | ||
| 655 | |||
| 656 | /// `part`, a slice of `image`, with the image's lifetime. | ||
| 657 | fn within<'a>(image: &'a [u8], part: &[u8]) -> &'a [u8] { | ||
| 658 | let start = part.as_ptr().addr() - image.as_ptr().addr(); | ||
| 659 | &image[start..start + part.len()] | ||
| 660 | } | ||
| 661 | |||
| 662 | fn one_page(view: &Revision<'_>, pages: &[ExGuid]) -> Result<Page> { | ||
| 663 | let [page] = pages else { | ||
| 664 | return Err(Error { | ||
| 665 | offset: 0, | ||
| 666 | message: "Choose an object space containing one active page", | ||
| 667 | }); | ||
| 668 | }; | ||
| 669 | Page::from_revision(view, *page) | ||
| 670 | } | ||
| 671 | |||
| 672 | #[cfg(test)] | ||
| 673 | mod tests { | ||
| 674 | use super::*; | ||
| 675 | use crate::{Insertion, active::tests::Writes, document::Document, write::GUIDS}; | ||
| 676 | |||
| 677 | thread_local! { | ||
| 678 | /// Changes a transaction after it is built and before it is checked. | ||
| 679 | pub(super) static TAMPER: std::cell::Cell<Option<fn(&mut Transaction)>> = | ||
| 680 | const { std::cell::Cell::new(None) }; | ||
| 681 | } | ||
| 682 | |||
| 683 | /// `f` with `fresh_guid` counting from `seed`. | ||
| 684 | fn seeded<T>(seed: u64, f: impl FnOnce() -> T) -> T { | ||
| 685 | let outer = GUIDS.replace(Some(seed)); | ||
| 686 | let result = f(); | ||
| 687 | GUIDS.set(outer); | ||
| 688 | result | ||
| 689 | } | ||
| 690 | |||
| 691 | /// The page spaces of an image, fullest first. | ||
| 692 | fn page_spaces(image: &[u8]) -> Vec<ExGuid> { | ||
| 693 | let store = Store::parse(image).unwrap(); | ||
| 694 | let index = RevisionIndex::parse(&store).unwrap(); | ||
| 695 | let document = Document::parse(&index).unwrap(); | ||
| 696 | let mut spaces: Vec<_> = document | ||
| 697 | .pages() | ||
| 698 | .unwrap() | ||
| 699 | .into_iter() | ||
| 700 | .map(|(space, _)| (document.active(space).unwrap().nodes.len(), space)) | ||
| 701 | .collect(); | ||
| 702 | spaces.sort_by(|a, b| b.cmp(a)); | ||
| 703 | spaces.into_iter().map(|(_, space)| space).collect() | ||
| 704 | } | ||
| 705 | |||
| 706 | /// Applies seeded batches of typed-writer changes to the pages of `source` through a | ||
| 707 | /// `Section` and through the writer that parses the image and squashes a batch into | ||
| 708 | /// one revision, requiring equal bytes after every seal. | ||
| 709 | fn differential( | ||
| 710 | source: &[u8], | ||
| 711 | seed: u64, | ||
| 712 | batches: usize, | ||
| 713 | largest: usize, | ||
| 714 | pages: usize, | ||
| 715 | ) -> usize { | ||
| 716 | let arena = Arena::default(); | ||
| 717 | let mut section = Section::open(&arena, source.to_vec()).unwrap(); | ||
| 718 | let spaces = page_spaces(source); | ||
| 719 | let mut image = source.to_vec(); | ||
| 720 | let mut writes = Writes(seed); | ||
| 721 | // New objects count from far above the identities builds draw. | ||
| 722 | GUIDS.set(Some(1 << 62 | seed << 40)); | ||
| 723 | let (mut sealed, mut checkpoints) = (0, 0); | ||
| 724 | for batch in 0..batches { | ||
| 725 | let space = spaces[batch % spaces.len().min(pages)]; | ||
| 726 | let store = Store::parse(&image).unwrap(); | ||
| 727 | let index = RevisionIndex::parse(&store).unwrap(); | ||
| 728 | let bump = Bump::new(); | ||
| 729 | let mut legacy = ActivePage::parse(&index, space).unwrap(); | ||
| 730 | for _ in 0..1 + (writes.0 as usize >> 40) % largest { | ||
| 731 | let Ok(changes) = writes.next(section.active(space).unwrap()) else { | ||
| 732 | continue; | ||
| 733 | }; | ||
| 734 | let stored = legacy.write(&bump, &[], changes.clone()).unwrap(); | ||
| 735 | assert_eq!( | ||
| 736 | section.apply_changes(space, &[], changes).unwrap(), | ||
| 737 | stored, | ||
| 738 | "batch {batch}" | ||
| 739 | ); | ||
| 740 | } | ||
| 741 | let guids = (batch as u64 + 1) << 32; | ||
| 742 | let expected = seeded(guids, || { | ||
| 743 | crate::page::write::squash( | ||
| 744 | &index, | ||
| 745 | &[(space, &legacy.live.revision)], | ||
| 746 | &legacy.payloads, | ||
| 747 | &BTreeMap::new(), | ||
| 748 | None, | ||
| 749 | ) | ||
| 750 | .unwrap() | ||
| 751 | }); | ||
| 752 | let before = section.stamp().clone(); | ||
| 753 | let chains = depths(&section); | ||
| 754 | let transaction = seeded(guids, || section.seal().unwrap()); | ||
| 755 | checkpoints += depths(&section) | ||
| 756 | .iter() | ||
| 757 | .filter(|(id, depth)| chains.get(id).is_some_and(|before| before > depth)) | ||
| 758 | .count(); | ||
| 759 | let mut written = image.clone(); | ||
| 760 | if let Some(transaction) = &transaction { | ||
| 761 | assert_eq!(transaction.base, before); | ||
| 762 | transaction.apply(&mut written).unwrap(); | ||
| 763 | sealed += 1; | ||
| 764 | } | ||
| 765 | assert!(written == expected, "batch {batch}"); | ||
| 766 | assert_eq!(Stamp::of(&written).unwrap(), *section.stamp()); | ||
| 767 | drop(legacy); | ||
| 768 | drop(index); | ||
| 769 | drop(store); | ||
| 770 | image = expected; | ||
| 771 | if batch % 25 == 0 || batch + 1 == batches { | ||
| 772 | assert!(section.image() == image); | ||
| 773 | let store = Store::parse(&image).unwrap(); | ||
| 774 | let index = RevisionIndex::parse(&store).unwrap(); | ||
| 775 | index.validate_current().unwrap(); | ||
| 776 | let document = Document::parse(&index).unwrap(); | ||
| 777 | for space in &spaces { | ||
| 778 | assert_eq!( | ||
| 779 | section.page(*space).unwrap(), | ||
| 780 | Page::from_space(&document, *space).unwrap() | ||
| 781 | ); | ||
| 782 | } | ||
| 783 | } | ||
| 784 | } | ||
| 785 | GUIDS.set(None); | ||
| 786 | assert!(sealed * 3 > batches, "{sealed} of {batches} batches sealed"); | ||
| 787 | checkpoints | ||
| 788 | } | ||
| 789 | |||
| 790 | /// Chain depths of the spaces a section has opened. | ||
| 791 | fn depths(section: &Section<'_>) -> BTreeMap<ExGuid, usize> { | ||
| 792 | section | ||
| 793 | .spaces | ||
| 794 | .iter() | ||
| 795 | .filter_map(|(id, space)| match &space.state { | ||
| 796 | SpaceState::Open(open) => Some((*id, open.file.depth)), | ||
| 797 | SpaceState::Stored { .. } => None, | ||
| 798 | }) | ||
| 799 | .collect() | ||
| 800 | } | ||
| 801 | |||
| 802 | #[test] | ||
| 803 | fn seals_append_the_bytes_the_image_writer_does() { | ||
| 804 | let corpus = [ | ||
| 805 | include_bytes!( | ||
| 806 | "../../../corpus/native/20260905-05/snapshots/02-text/notebook/synthetic.one" | ||
| 807 | ) | ||
| 808 | .as_slice(), | ||
| 809 | include_bytes!("../../../corpus/m6/native-features-01/notebook/Features.one"), | ||
| 810 | include_bytes!("../../../corpus/outline-edit/tree/before/notebook/synthetic.one"), | ||
| 811 | include_bytes!("../../../corpus/paragraph-edit/before/notebook/synthetic.one"), | ||
| 812 | ]; | ||
| 813 | for (seed, source) in corpus.iter().enumerate() { | ||
| 814 | differential(source, seed as u64, 60, 1, 2); | ||
| 815 | differential(source, seed as u64 + 100, 60, 5, 2); | ||
| 816 | } | ||
| 817 | let created = crate::create_section("model.one", "First", "Author").unwrap(); | ||
| 818 | differential(&created, 7, 60, 3, 1); | ||
| 819 | } | ||
| 820 | |||
| 821 | #[test] | ||
| 822 | fn wide_batches_regroup_and_alias_as_the_image_writer_does() { | ||
| 823 | // These seeds make a seal alias read-only objects or group tables across writes | ||
| 824 | // differently from the writes themselves, so the pages adopt the sealed form. | ||
| 825 | let corpus = [ | ||
| 826 | include_bytes!("../../../corpus/m6/native-features-01/notebook/Features.one") | ||
| 827 | .as_slice(), | ||
| 828 | include_bytes!("../../../corpus/outline-edit/tree/before/notebook/synthetic.one"), | ||
| 829 | include_bytes!("../../../corpus/paragraph-edit/before/notebook/synthetic.one"), | ||
| 830 | ]; | ||
| 831 | for seed in 4..=8 { | ||
| 832 | for source in corpus { | ||
| 833 | differential(source, seed, 30, 12, 3); | ||
| 834 | } | ||
| 835 | } | ||
| 836 | } | ||
| 837 | |||
| 838 | #[test] | ||
| 839 | fn a_long_chain_checkpoints_as_the_image_writer_does() { | ||
| 840 | // Past 512 revisions of one space, so the chain checkpoints once. | ||
| 841 | let checkpoints = differential( | ||
| 842 | include_bytes!( | ||
| 843 | "../../../corpus/native/20260905-05/snapshots/02-text/notebook/synthetic.one" | ||
| 844 | ), | ||
| 845 | 11, | ||
| 846 | 560, | ||
| 847 | 2, | ||
| 848 | 1, | ||
| 849 | ); | ||
| 850 | assert_eq!(checkpoints, 1); | ||
| 851 | } | ||
| 852 | |||
| 853 | #[test] | ||
| 854 | fn a_created_space_seals_as_the_image_writer_creates_it() { | ||
| 855 | let source = &crate::create_section("model.one", "First", "Author").unwrap(); | ||
| 856 | let store = Store::parse(source).unwrap(); | ||
| 857 | let index = RevisionIndex::parse(&store).unwrap(); | ||
| 858 | // A page without history, whose objects name no other space or context. | ||
| 859 | let (copied, revision, reachable) = page_spaces(source) | ||
| 860 | .into_iter() | ||
| 861 | .map(|space| { | ||
| 862 | let revision = index.resolve_active(space).unwrap(); | ||
| 863 | let reachable = revision.reachable().unwrap(); | ||
| 864 | (space, revision, reachable) | ||
| 865 | }) | ||
| 866 | .find(|(_, revision, reachable)| { | ||
| 867 | reachable.iter().all(|id| { | ||
| 868 | let references = revision.objects[id].references().unwrap(); | ||
| 869 | references.object_spaces.is_empty() && references.contexts.is_empty() | ||
| 870 | }) | ||
| 871 | }) | ||
| 872 | .unwrap(); | ||
| 873 | let objects: Changes = reachable | ||
| 874 | .iter() | ||
| 875 | .map(|id| (*id, replacement(*id, &revision.objects[id]).unwrap())) | ||
| 876 | .collect(); | ||
| 877 | let space = ExGuid { | ||
| 878 | guid: [7; 16], | ||
| 879 | n: 0, | ||
| 880 | }; | ||
| 881 | let expected = seeded(1, || { | ||
| 882 | crate::write::write_revisions(source, |_| { | ||
| 883 | Ok(BTreeMap::from([( | ||
| 884 | space, | ||
| 885 | crate::write::RevisionEdit::Create { | ||
| 886 | roots: revision.roots.clone(), | ||
| 887 | objects: objects.clone(), | ||
| 888 | }, | ||
| 889 | )])) | ||
| 890 | }) | ||
| 891 | .unwrap() | ||
| 892 | }); | ||
| 893 | let arena = Arena::default(); | ||
| 894 | let mut section = Section::open(&arena, source.to_vec()).unwrap(); | ||
| 895 | section | ||
| 896 | .create_space(space, revision.roots.clone(), objects) | ||
| 897 | .unwrap(); | ||
| 898 | assert_eq!(section.page(space).unwrap(), section.page(copied).unwrap()); | ||
| 899 | seeded(1, || section.seal().unwrap()).unwrap(); | ||
| 900 | assert!(section.image() == expected); | ||
| 901 | // The space's next revision depends on the one creating it. | ||
| 902 | let mut writes = Writes(5); | ||
| 903 | let mut image = expected; | ||
| 904 | let mut written = 0; | ||
| 905 | for batch in 0..6 { | ||
| 906 | let Ok(changes) = writes.next(section.active(space).unwrap()) else { | ||
| 907 | continue; | ||
| 908 | }; | ||
| 909 | written += 1; | ||
| 910 | section.apply_changes(space, &[], changes.clone()).unwrap(); | ||
| 911 | let guids = (batch + 2) << 32; | ||
| 912 | let store = Store::parse(&image).unwrap(); | ||
| 913 | let index = RevisionIndex::parse(&store).unwrap(); | ||
| 914 | let bump = Bump::new(); | ||
| 915 | // The root lists no page in this space, so no document reaches it. | ||
| 916 | let rid = index.active(space).unwrap(); | ||
| 917 | let live = LiveRevision::new( | ||
| 918 | index.resolve(space, rid).unwrap(), | ||
| 919 | chain_depth(&index, space, rid), | ||
| 920 | ) | ||
| 921 | .unwrap(); | ||
| 922 | let store = &store; | ||
| 923 | let files: Files<'_> = std::rc::Rc::new(|guid| store.file_data(guid)); | ||
| 924 | let mut legacy = ActivePage::open(space, live, FileType::Section, files).unwrap(); | ||
| 925 | legacy.write(&bump, &[], changes).unwrap(); | ||
| 926 | let expected = seeded(guids, || { | ||
| 927 | let revision = &legacy.live.revision; | ||
| 928 | crate::page::write::squash( | ||
| 929 | &index, | ||
| 930 | &[(space, revision)], | ||
| 931 | &[], | ||
| 932 | &BTreeMap::new(), | ||
| 933 | None, | ||
| 934 | ) | ||
| 935 | .unwrap() | ||
| 936 | }); | ||
| 937 | drop(legacy); | ||
| 938 | drop(index); | ||
| 939 | image = expected; | ||
| 940 | seeded(guids, || section.seal().unwrap()); | ||
| 941 | assert!(section.image() == image, "batch {batch}"); | ||
| 942 | } | ||
| 943 | assert!(written > 2); | ||
| 944 | let reopened = Section::open(&arena, image).unwrap(); | ||
| 945 | assert_eq!(reopened.page(space).unwrap(), section.page(space).unwrap()); | ||
| 946 | } | ||
| 947 | |||
| 948 | #[test] | ||
| 949 | fn payloads_embed_once_as_the_image_writer_embeds_them() { | ||
| 950 | let source = include_bytes!("../../../corpus/m6/native-features-01/notebook/Features.one"); | ||
| 951 | let space = page_spaces(source)[0]; | ||
| 952 | let payload = ([9; 16], b"payload bytes".as_slice()); | ||
| 953 | let arena = Arena::default(); | ||
| 954 | let mut section = Section::open(&arena, source.to_vec()).unwrap(); | ||
| 955 | let mut image = source.to_vec(); | ||
| 956 | for seed in [1_u64 << 32, 2 << 32] { | ||
| 957 | assert!( | ||
| 958 | section | ||
| 959 | .apply_changes(space, &[payload], Changes::new()) | ||
| 960 | .unwrap() | ||
| 961 | ); | ||
| 962 | let store = Store::parse(&image).unwrap(); | ||
| 963 | let index = RevisionIndex::parse(&store).unwrap(); | ||
| 964 | let legacy = ActivePage::parse(&index, space).unwrap(); | ||
| 965 | let expected = seeded(seed, || { | ||
| 966 | let revision = &legacy.live.revision; | ||
| 967 | crate::page::write::squash( | ||
| 968 | &index, | ||
| 969 | &[(space, revision)], | ||
| 970 | &[payload], | ||
| 971 | &BTreeMap::new(), | ||
| 972 | None, | ||
| 973 | ) | ||
| 974 | .unwrap() | ||
| 975 | }); | ||
| 976 | let transaction = seeded(seed, || section.seal().unwrap()); | ||
| 977 | assert_eq!(transaction.is_some(), expected != image); | ||
| 978 | assert!(section.image() == expected); | ||
| 979 | drop(legacy); | ||
| 980 | drop(index); | ||
| 981 | drop(store); | ||
| 982 | image = expected; | ||
| 983 | } | ||
| 984 | let store = Store::parse(&image).unwrap(); | ||
| 985 | assert_eq!(store.file_data(payload.0).unwrap(), payload.1); | ||
| 986 | } | ||
| 987 | |||
| 988 | #[test] | ||
| 989 | fn a_seal_checks_the_bytes_it_appends() { | ||
| 990 | fn utf16(text: &str) -> Vec<u8> { | ||
| 991 | text.encode_utf16().flat_map(u16::to_le_bytes).collect() | ||
| 992 | } | ||
| 993 | fn object_bytes(transaction: &mut Transaction) { | ||
| 994 | let text = utf16("Tampered text"); | ||
| 995 | let at = transaction | ||
| 996 | .append | ||
| 997 | .windows(text.len()) | ||
| 998 | .position(|w| w == text) | ||
| 999 | .unwrap(); | ||
| 1000 | transaction.append[at] ^= 1; | ||
| 1001 | } | ||
| 1002 | fn link(transaction: &mut Transaction) { | ||
| 1003 | let (_, bytes) = transaction | ||
| 1004 | .patches | ||
| 1005 | .iter_mut() | ||
| 1006 | .find(|(_, b)| b.len() == 12) | ||
| 1007 | .unwrap(); | ||
| 1008 | bytes[0] ^= 8; | ||
| 1009 | } | ||
| 1010 | fn sentinel(transaction: &mut Transaction) { | ||
| 1011 | let (_, bytes) = transaction.patches.last_mut().unwrap(); | ||
| 1012 | *bytes.last_mut().unwrap() ^= 1; | ||
| 1013 | } | ||
| 1014 | fn header(transaction: &mut Transaction) { | ||
| 1015 | transaction.header[96] ^= 2; | ||
| 1016 | } | ||
| 1017 | let source = include_bytes!("../../../corpus/m6/native-features-01/notebook/Features.one"); | ||
| 1018 | let space = page_spaces(source)[0]; | ||
| 1019 | let seal = |tamper: Option<fn(&mut Transaction)>| { | ||
| 1020 | let arena = Arena::default(); | ||
| 1021 | let mut section = Section::open(&arena, source.to_vec()).unwrap(); | ||
| 1022 | let page = section.active(space).unwrap(); | ||
| 1023 | let changes = page | ||
| 1024 | .view | ||
| 1025 | .nodes | ||
| 1026 | .iter() | ||
| 1027 | .filter(|(id, node)| { | ||
| 1028 | matches!(node.kind, crate::document::Kind::Outline { .. }) | ||
| 1029 | && page.parents.contains_key(id) | ||
| 1030 | }) | ||
| 1031 | .find_map(|(outline, _)| { | ||
| 1032 | Insertion::paragraph(*outline, None, "Tampered text", "Author") | ||
| 1033 | .and_then(|insertion| insertion.changes(page)) | ||
| 1034 | .ok() | ||
| 1035 | }) | ||
| 1036 | .unwrap(); | ||
| 1037 | section.apply_changes(space, &[], changes).unwrap(); | ||
| 1038 | TAMPER.set(tamper); | ||
| 1039 | let sealed = section.seal(); | ||
| 1040 | TAMPER.set(None); | ||
| 1041 | let error = sealed.as_ref().err().map(|error| error.message); | ||
| 1042 | assert_eq!(section.seal().is_err(), error.is_some()); | ||
| 1043 | error | ||
| 1044 | }; | ||
| 1045 | assert_eq!(seal(None), None); | ||
| 1046 | for tamper in [object_bytes, link, sentinel, header] { | ||
| 1047 | assert!(seal(Some(tamper)).is_some()); | ||
| 1048 | } | ||
| 1049 | } | ||
| 1050 | |||
| 1051 | #[test] | ||
| 1052 | fn a_replayed_transaction_leaves_the_sealing_section() { | ||
| 1053 | let source = include_bytes!("../../../corpus/m6/native-features-01/notebook/Features.one"); | ||
| 1054 | let space = page_spaces(source)[0]; | ||
| 1055 | let arena = Arena::default(); | ||
| 1056 | let mut sealing = Section::open(&arena, source.to_vec()).unwrap(); | ||
| 1057 | let mut replaying = Section::open(&arena, source.to_vec()).unwrap(); | ||
| 1058 | let mut writes = Writes(3); | ||
| 1059 | for _ in 0..8 { | ||
| 1060 | for _ in 0..3 { | ||
| 1061 | if let Ok(changes) = writes.next(sealing.active(space).unwrap()) { | ||
| 1062 | sealing.apply_changes(space, &[], changes).unwrap(); | ||
| 1063 | } | ||
| 1064 | } | ||
| 1065 | let Some(transaction) = sealing.seal().unwrap() else { | ||
| 1066 | continue; | ||
| 1067 | }; | ||
| 1068 | let queued: Transaction = | ||
| 1069 | serde_json::from_str(&serde_json::to_string(&transaction).unwrap()).unwrap(); | ||
| 1070 | assert_eq!(queued, transaction); | ||
| 1071 | replaying.replay(&queued).unwrap(); | ||
| 1072 | assert_eq!(replaying.stamp(), sealing.stamp()); | ||
| 1073 | assert!(replaying.image() == sealing.image()); | ||
| 1074 | assert_eq!(replaying.page(space).unwrap(), sealing.page(space).unwrap()); | ||
| 1075 | } | ||
| 1076 | } | ||
| 1077 | } | ||
| 1078 | |||
| 1079 | #[cfg(test)] | ||
| 1080 | mod probe { | ||
| 1081 | use super::*; | ||
| 1082 | use crate::{document::Kind, edit::text_changes}; | ||
| 1083 | use std::time::{Duration, Instant}; | ||
| 1084 | |||
| 1085 | fn median(mut samples: Vec<Duration>) -> Duration { | ||
| 1086 | samples.sort(); | ||
| 1087 | samples[samples.len() / 2] | ||
| 1088 | } | ||
| 1089 | |||
| 1090 | /// The fullest page space of `image` and a rich-text object in its middle. | ||
| 1091 | fn target(image: &[u8]) -> (ExGuid, ExGuid, usize) { | ||
| 1092 | let store = Store::parse(image).unwrap(); | ||
| 1093 | let index = RevisionIndex::parse(&store).unwrap(); | ||
| 1094 | let document = crate::document::Document::parse(&index).unwrap(); | ||
| 1095 | let texts = |space: ExGuid| -> Vec<ExGuid> { | ||
| 1096 | document | ||
| 1097 | .active(space) | ||
| 1098 | .unwrap() | ||
| 1099 | .nodes | ||
| 1100 | .iter() | ||
| 1101 | .filter(|(_, node)| { | ||
| 1102 | matches!( | ||
| 1103 | node.kind, | ||
| 1104 | Kind::RichText { | ||
| 1105 | boilerplate: false, | ||
| 1106 | .. | ||
| 1107 | } | ||
| 1108 | ) | ||
| 1109 | }) | ||
| 1110 | .map(|(id, _)| *id) | ||
| 1111 | .collect() | ||
| 1112 | }; | ||
| 1113 | let (space, _) = document | ||
| 1114 | .pages() | ||
| 1115 | .unwrap() | ||
| 1116 | .into_iter() | ||
| 1117 | .max_by_key(|(space, _)| texts(*space).len()) | ||
| 1118 | .unwrap(); | ||
| 1119 | let texts = texts(space); | ||
| 1120 | (space, texts[texts.len() / 2], texts.len()) | ||
| 1121 | } | ||
| 1122 | |||
| 1123 | /// One-character edits of a large page through the image writer and through a | ||
| 1124 | /// `Section`: `SECTION_PROBE=path cargo test --release -p onestore --lib probe -- --ignored --nocapture`. | ||
| 1125 | #[test] | ||
| 1126 | #[ignore] | ||
| 1127 | fn keystrokes() { | ||
| 1128 | let path = std::env::var("SECTION_PROBE").unwrap_or("/tmp/probe3000.one".into()); | ||
| 1129 | let image = std::fs::read(&path).unwrap(); | ||
| 1130 | let (space, text, count) = target(&image); | ||
| 1131 | println!( | ||
| 1132 | "{path}: {} bytes, {count} text objects on the page", | ||
| 1133 | image.len() | ||
| 1134 | ); | ||
| 1135 | |||
| 1136 | let mut samples = Vec::new(); | ||
| 1137 | let mut written = 0; | ||
| 1138 | for _ in 0..9 { | ||
| 1139 | let start = Instant::now(); | ||
| 1140 | let edit = crate::PreparedEdit::text(&image, space, text, 0..0, "x").unwrap(); | ||
| 1141 | samples.push(start.elapsed()); | ||
| 1142 | let transaction = edit.transaction(); | ||
| 1143 | written = transaction.append.len() | ||
| 1144 | + transaction | ||
| 1145 | .patches | ||
| 1146 | .iter() | ||
| 1147 | .map(|(_, bytes)| bytes.len()) | ||
| 1148 | .sum::<usize>(); | ||
| 1149 | } | ||
| 1150 | println!( | ||
| 1151 | "image writer, one keystroke: {:?}, {written} bytes written", | ||
| 1152 | median(samples) | ||
| 1153 | ); | ||
| 1154 | |||
| 1155 | let before = { | ||
| 1156 | let store = Store::parse(&image).unwrap(); | ||
| 1157 | let index = RevisionIndex::parse(&store).unwrap(); | ||
| 1158 | Page::from_space(&crate::document::Document::parse(&index).unwrap(), space).unwrap() | ||
| 1159 | }; | ||
| 1160 | let mut after = before.clone(); | ||
| 1161 | let edited = after | ||
| 1162 | .objects | ||
| 1163 | .iter_mut() | ||
| 1164 | .filter_map(|object| match object { | ||
| 1165 | crate::page::PageObject::Outline(outline) => Some(outline), | ||
| 1166 | _ => None, | ||
| 1167 | }) | ||
| 1168 | .flat_map(|outline| &mut outline.paragraphs) | ||
| 1169 | .find_map(|paragraph| paragraph.text_mut().filter(|object| object.id == text)) | ||
| 1170 | .unwrap(); | ||
| 1171 | let format = edited.text.format_at(0).unwrap().clone(); | ||
| 1172 | edited | ||
| 1173 | .text | ||
| 1174 | .apply(crate::page::text::Edit { | ||
| 1175 | range: 0..0, | ||
| 1176 | replacement: crate::page::Paragraph::new("x".into(), format), | ||
| 1177 | }) | ||
| 1178 | .unwrap(); | ||
| 1179 | let mut samples = Vec::new(); | ||
| 1180 | for _ in 0..9 { | ||
| 1181 | let start = Instant::now(); | ||
| 1182 | crate::PreparedEdit::page(&image, space, &after, "Probe").unwrap(); | ||
| 1183 | samples.push(start.elapsed()); | ||
| 1184 | } | ||
| 1185 | println!("page-model writer, one keystroke: {:?}", median(samples)); | ||
| 1186 | |||
| 1187 | let arena = Arena::default(); | ||
| 1188 | let start = Instant::now(); | ||
| 1189 | let mut section = Section::open(&arena, image.clone()).unwrap(); | ||
| 1190 | println!("Section::open: {:?}", start.elapsed()); | ||
| 1191 | let start = Instant::now(); | ||
| 1192 | section.active(space).unwrap(); | ||
| 1193 | println!("first edit opens the page: {:?}", start.elapsed()); | ||
| 1194 | let (mut applies, mut seals, mut sizes) = (Vec::new(), Vec::new(), Vec::new()); | ||
| 1195 | for keystroke in 0..200u32 { | ||
| 1196 | let start = Instant::now(); | ||
| 1197 | let changes = text_changes( | ||
| 1198 | section.active(space).unwrap(), | ||
| 1199 | text, | ||
| 1200 | keystroke..keystroke, | ||
| 1201 | "x", | ||
| 1202 | ) | ||
| 1203 | .unwrap(); | ||
| 1204 | section.apply_changes(space, &[], changes).unwrap(); | ||
| 1205 | applies.push(start.elapsed()); | ||
| 1206 | let start = Instant::now(); | ||
| 1207 | let transaction = section.seal().unwrap().unwrap(); | ||
| 1208 | seals.push(start.elapsed()); | ||
| 1209 | sizes.push(( | ||
| 1210 | transaction.append.len(), | ||
| 1211 | transaction | ||
| 1212 | .patches | ||
| 1213 | .iter() | ||
| 1214 | .map(|(_, bytes)| bytes.len()) | ||
| 1215 | .sum::<usize>(), | ||
| 1216 | )); | ||
| 1217 | } | ||
| 1218 | println!( | ||
| 1219 | "first seal: appended {} bytes, patched {} bytes", | ||
| 1220 | sizes[0].0, sizes[0].1 | ||
| 1221 | ); | ||
| 1222 | sizes.sort(); | ||
| 1223 | println!( | ||
| 1224 | "Section, one keystroke: apply {:?}, seal {:?}; appended {:?} bytes, patched {:?} bytes (median)", | ||
| 1225 | median(applies), | ||
| 1226 | median(seals), | ||
| 1227 | sizes[sizes.len() / 2].0, | ||
| 1228 | sizes[sizes.len() / 2].1, | ||
| 1229 | ); | ||
| 1230 | let start = Instant::now(); | ||
| 1231 | let reopened = Section::open(&arena, section.image()).unwrap(); | ||
| 1232 | println!("reopen after 200 seals: {:?}", start.elapsed()); | ||
| 1233 | assert_eq!(reopened.page(space).unwrap(), section.page(space).unwrap()); | ||
| 1234 | } | ||
| 1235 | } | ||
crates/onestore/src/snapshot.rs+1-1| ... | @@ -1,7 +1,7 @@ | ... | @@ -1,7 +1,7 @@ |
| 1 | use crate::{RevisionIndex, Store}; | 1 | use crate::{RevisionIndex, Store}; |
| 2 | use std::io; | 2 | use std::io; |
| 3 | 3 | ||
| 4 | fn read_exact( | 4 | pub(crate) fn read_exact( |
| 5 | read: &mut impl FnMut(u64, &mut [u8]) -> io::Result<usize>, | 5 | read: &mut impl FnMut(u64, &mut [u8]) -> io::Result<usize>, |
| 6 | mut offset: u64, | 6 | mut offset: u64, |
| 7 | mut output: &mut [u8], | 7 | mut output: &mut [u8], |
crates/onestore/src/store.rs+264-114| ... | @@ -219,6 +219,176 @@ pub struct NodeList<'a> { | ... | @@ -219,6 +219,176 @@ pub struct NodeList<'a> { |
| 219 | pub nodes: Vec<Node<'a>>, | 219 | pub nodes: Vec<Node<'a>>, |
| 220 | } | 220 | } |
| 221 | 221 | ||
| 222 | /// A file-node list fragment (MS-ONESTORE 2.4.1) before its nodes are decoded. | ||
| 223 | pub(crate) struct Fragment<'a> { | ||
| 224 | pub id: u32, | ||
| 225 | pub sequence: u32, | ||
| 226 | pub next: Chunk, | ||
| 227 | body: Cursor<'a>, | ||
| 228 | } | ||
| 229 | |||
| 230 | impl<'a> Fragment<'a> { | ||
| 231 | /// The fragment `bytes`, which start at file offset `offset`. | ||
| 232 | pub(crate) fn parse(bytes: &'a [u8], offset: usize) -> Result<Self> { | ||
| 233 | if bytes.len() < 36 { | ||
| 234 | return Err(Error { | ||
| 235 | offset, | ||
| 236 | message: "Truncated file-node fragment", | ||
| 237 | }); | ||
| 238 | } | ||
| 239 | let mut c = Cursor { bytes, offset }; | ||
| 240 | if u64::from_le_bytes(c.read()?) != 0xa4567ab1f5f7f4c4 { | ||
| 241 | return Err(Error { | ||
| 242 | offset, | ||
| 243 | message: "Incorrect file-node fragment signature", | ||
| 244 | }); | ||
| 245 | } | ||
| 246 | let id = u32::from_le_bytes(c.read()?); | ||
| 247 | let sequence = u32::from_le_bytes(c.read()?); | ||
| 248 | let end = offset + bytes.len(); | ||
| 249 | let mut tail = Cursor { | ||
| 250 | bytes: &bytes[bytes.len() - 20..], | ||
| 251 | offset: end - 20, | ||
| 252 | }; | ||
| 253 | let next = tail.chunk()?; | ||
| 254 | if u64::from_le_bytes(tail.read()?) != 0x8bc215c38233ba4b { | ||
| 255 | return Err(Error { | ||
| 256 | offset: end - 8, | ||
| 257 | message: "Incorrect file-node fragment footer", | ||
| 258 | }); | ||
| 259 | } | ||
| 260 | Ok(Self { | ||
| 261 | id, | ||
| 262 | sequence, | ||
| 263 | next, | ||
| 264 | body: Cursor { | ||
| 265 | bytes: &bytes[16..bytes.len() - 20], | ||
| 266 | offset: offset + 16, | ||
| 267 | }, | ||
| 268 | }) | ||
| 269 | } | ||
| 270 | |||
| 271 | /// Decodes nodes into `nodes` until it holds `required` or the fragment ends; `data` | ||
| 272 | /// checks each data reference. | ||
| 273 | pub(crate) fn nodes( | ||
| 274 | self, | ||
| 275 | required: usize, | ||
| 276 | nodes: &mut Vec<Node<'a>>, | ||
| 277 | data: impl Fn(Chunk) -> Result<()>, | ||
| 278 | ) -> Result<()> { | ||
| 279 | let mut c = self.body; | ||
| 280 | while nodes.len() < required && c.bytes.len() >= 4 { | ||
| 281 | let offset = c.offset; | ||
| 282 | let raw = u32::from_le_bytes(c.read()?); | ||
| 283 | let size = usize::try_from((raw >> 10) & 0x1fff).unwrap(); | ||
| 284 | if size < 4 { | ||
| 285 | return Err(Error { | ||
| 286 | offset, | ||
| 287 | message: "File node is shorter than its header", | ||
| 288 | }); | ||
| 289 | } | ||
| 290 | let id = u16::try_from(raw & 0x3ff).unwrap(); | ||
| 291 | let body = c.take(size - 4)?; | ||
| 292 | if id == 0xff { | ||
| 293 | break; | ||
| 294 | } | ||
| 295 | let mut fields = Cursor { | ||
| 296 | bytes: body, | ||
| 297 | offset: offset + 4, | ||
| 298 | }; | ||
| 299 | let reference = match (raw >> 27) & 0xf { | ||
| 300 | 0 => None, | ||
| 301 | base @ (1 | 2) => { | ||
| 302 | let (stp, nil, shift) = match (raw >> 23) & 3 { | ||
| 303 | 0 => (u64::from_le_bytes(fields.read()?), u64::MAX, 0), | ||
| 304 | 1 => ( | ||
| 305 | u64::from(u32::from_le_bytes(fields.read()?)), | ||
| 306 | u64::from(u32::MAX), | ||
| 307 | 0, | ||
| 308 | ), | ||
| 309 | 2 => ( | ||
| 310 | u64::from(u16::from_le_bytes(fields.read()?)), | ||
| 311 | u64::from(u16::MAX), | ||
| 312 | 3, | ||
| 313 | ), | ||
| 314 | 3 => ( | ||
| 315 | u64::from(u32::from_le_bytes(fields.read()?)), | ||
| 316 | u64::from(u32::MAX), | ||
| 317 | 3, | ||
| 318 | ), | ||
| 319 | _ => unreachable!(), | ||
| 320 | }; | ||
| 321 | let cb = match (raw >> 25) & 3 { | ||
| 322 | 0 => u64::from(u32::from_le_bytes(fields.read()?)), | ||
| 323 | 1 => u64::from_le_bytes(fields.read()?), | ||
| 324 | 2 => u64::from(fields.read::<1>()?[0]) * 8, | ||
| 325 | 3 => u64::from(u16::from_le_bytes(fields.read()?)) * 8, | ||
| 326 | _ => unreachable!(), | ||
| 327 | }; | ||
| 328 | let reference = Chunk { | ||
| 329 | offset: if cb == 0 && stp == nil { | ||
| 330 | u64::MAX | ||
| 331 | } else { | ||
| 332 | stp << shift | ||
| 333 | }, | ||
| 334 | length: cb, | ||
| 335 | }; | ||
| 336 | if base == 1 { | ||
| 337 | if !reference.absent() { | ||
| 338 | data(reference)?; | ||
| 339 | } | ||
| 340 | Some(Reference::Data(reference)) | ||
| 341 | } else { | ||
| 342 | Some(Reference::NodeList(reference)) | ||
| 343 | } | ||
| 344 | } | ||
| 345 | _ => { | ||
| 346 | return Err(Error { | ||
| 347 | offset, | ||
| 348 | message: "Unsupported file-node base type", | ||
| 349 | }); | ||
| 350 | } | ||
| 351 | }; | ||
| 352 | nodes.push(Node { | ||
| 353 | id, | ||
| 354 | offset, | ||
| 355 | payload: fields.bytes, | ||
| 356 | reference, | ||
| 357 | }); | ||
| 358 | } | ||
| 359 | Ok(()) | ||
| 360 | } | ||
| 361 | } | ||
| 362 | |||
| 363 | /// Where a store's next transaction appends, as its committed transactions leave it. | ||
| 364 | #[derive(Clone, Debug)] | ||
| 365 | pub(crate) struct StoreState { | ||
| 366 | pub stamp: crate::Stamp, | ||
| 367 | pub file_type: FileType, | ||
| 368 | /// The last transaction-log fragment and the entry bytes it holds. | ||
| 369 | pub log: (Chunk, usize), | ||
| 370 | /// The log's checksum through its last entry. | ||
| 371 | pub log_crc: u32, | ||
| 372 | /// The highest file-node list identity the log names. | ||
| 373 | pub max_list: u32, | ||
| 374 | pub root: ListTail, | ||
| 375 | /// The file-data store list, once one exists. | ||
| 376 | pub files: Option<ListTail>, | ||
| 377 | /// Each object space's revision manifest list. | ||
| 378 | pub spaces: BTreeMap<crate::ExGuid, ListTail>, | ||
| 379 | } | ||
| 380 | |||
| 381 | /// The last fragment of a file-node list, where appending continues. | ||
| 382 | #[derive(Clone, Copy, Debug)] | ||
| 383 | pub(crate) struct ListTail { | ||
| 384 | pub id: u32, | ||
| 385 | pub fragments: u32, | ||
| 386 | pub last: Chunk, | ||
| 387 | pub nodes: usize, | ||
| 388 | /// The file offset past the list's last node. | ||
| 389 | pub end: u64, | ||
| 390 | } | ||
| 391 | |||
| 222 | #[derive(Debug)] | 392 | #[derive(Debug)] |
| 223 | pub(crate) struct TransactionFragment<'a> { | 393 | pub(crate) struct TransactionFragment<'a> { |
| 224 | pub chunk: Chunk, | 394 | pub chunk: Chunk, |
| ... | @@ -357,26 +527,9 @@ impl<'a> Store<'a> { | ... | @@ -357,26 +527,9 @@ impl<'a> Store<'a> { |
| 357 | loop { | 527 | loop { |
| 358 | let range = chunk.range(data)?; | 528 | let range = chunk.range(data)?; |
| 359 | claim(&mut occupied, range.clone())?; | 529 | claim(&mut occupied, range.clone())?; |
| 360 | if range.len() < 36 { | 530 | let fragment = Fragment::parse(&data[range.clone()], range.start)?; |
| 361 | return Err(Error { | 531 | if usize::try_from(fragment.sequence).ok() != Some(fragments.len()) |
| 362 | offset: range.start, | 532 | || list_id.is_some_and(|previous| fragment.id != previous) |
| 363 | message: "Truncated file-node fragment", | ||
| 364 | }); | ||
| 365 | } | ||
| 366 | let mut c = Cursor { | ||
| 367 | bytes: &data[range.clone()], | ||
| 368 | offset: range.start, | ||
| 369 | }; | ||
| 370 | if u64::from_le_bytes(c.read()?) != 0xa4567ab1f5f7f4c4 { | ||
| 371 | return Err(Error { | ||
| 372 | offset: range.start, | ||
| 373 | message: "Incorrect file-node fragment signature", | ||
| 374 | }); | ||
| 375 | } | ||
| 376 | let id = u32::from_le_bytes(c.read()?); | ||
| 377 | let sequence = u32::from_le_bytes(c.read()?); | ||
| 378 | if usize::try_from(sequence).ok() != Some(fragments.len()) | ||
| 379 | || list_id.is_some_and(|previous| id != previous) | ||
| 380 | { | 533 | { |
| 381 | return Err(Error { | 534 | return Err(Error { |
| 382 | offset: range.start + 8, | 535 | offset: range.start + 8, |
| ... | @@ -384,13 +537,13 @@ impl<'a> Store<'a> { | ... | @@ -384,13 +537,13 @@ impl<'a> Store<'a> { |
| 384 | }); | 537 | }); |
| 385 | } | 538 | } |
| 386 | if list_id.is_none() { | 539 | if list_id.is_none() { |
| 387 | if !list_ids.insert(id) { | 540 | if !list_ids.insert(fragment.id) { |
| 388 | return Err(Error { | 541 | return Err(Error { |
| 389 | offset: range.start + 8, | 542 | offset: range.start + 8, |
| 390 | message: "Duplicate file-node list identity", | 543 | message: "Duplicate file-node list identity", |
| 391 | }); | 544 | }); |
| 392 | } | 545 | } |
| 393 | required = usize::try_from(*counts.get(&id).ok_or(Error { | 546 | required = usize::try_from(*counts.get(&fragment.id).ok_or(Error { |
| 394 | offset: range.start + 8, | 547 | offset: range.start + 8, |
| 395 | message: "File-node list is absent from the transaction log", | 548 | message: "File-node list is absent from the transaction log", |
| 396 | })?) | 549 | })?) |
| ... | @@ -398,99 +551,12 @@ impl<'a> Store<'a> { | ... | @@ -398,99 +551,12 @@ impl<'a> Store<'a> { |
| 398 | offset: range.start + 8, | 551 | offset: range.start + 8, |
| 399 | message: "File-node count exceeds address space", | 552 | message: "File-node count exceeds address space", |
| 400 | })?; | 553 | })?; |
| 401 | list_id = Some(id); | 554 | list_id = Some(fragment.id); |
| 402 | } | ||
| 403 | let mut tail = Cursor { | ||
| 404 | bytes: &data[range.end - 20..range.end], | ||
| 405 | offset: range.end - 20, | ||
| 406 | }; | ||
| 407 | let next = tail.chunk()?; | ||
| 408 | if u64::from_le_bytes(tail.read()?) != 0x8bc215c38233ba4b { | ||
| 409 | return Err(Error { | ||
| 410 | offset: range.end - 8, | ||
| 411 | message: "Incorrect file-node fragment footer", | ||
| 412 | }); | ||
| 413 | } | ||
| 414 | c.bytes = &data[range.start + 16..range.end - 20]; | ||
| 415 | while nodes.len() < required && c.bytes.len() >= 4 { | ||
| 416 | let offset = c.offset; | ||
| 417 | let raw = u32::from_le_bytes(c.read()?); | ||
| 418 | let size = usize::try_from((raw >> 10) & 0x1fff).unwrap(); | ||
| 419 | if size < 4 { | ||
| 420 | return Err(Error { | ||
| 421 | offset, | ||
| 422 | message: "File node is shorter than its header", | ||
| 423 | }); | ||
| 424 | } | ||
| 425 | let id = u16::try_from(raw & 0x3ff).unwrap(); | ||
| 426 | let body = c.take(size - 4)?; | ||
| 427 | if id == 0xff { | ||
| 428 | break; | ||
| 429 | } | ||
| 430 | let mut fields = Cursor { | ||
| 431 | bytes: body, | ||
| 432 | offset: offset + 4, | ||
| 433 | }; | ||
| 434 | let reference = match (raw >> 27) & 0xf { | ||
| 435 | 0 => None, | ||
| 436 | base @ (1 | 2) => { | ||
| 437 | let (stp, nil, shift) = match (raw >> 23) & 3 { | ||
| 438 | 0 => (u64::from_le_bytes(fields.read()?), u64::MAX, 0), | ||
| 439 | 1 => ( | ||
| 440 | u64::from(u32::from_le_bytes(fields.read()?)), | ||
| 441 | u64::from(u32::MAX), | ||
| 442 | 0, | ||
| 443 | ), | ||
| 444 | 2 => ( | ||
| 445 | u64::from(u16::from_le_bytes(fields.read()?)), | ||
| 446 | u64::from(u16::MAX), | ||
| 447 | 3, | ||
| 448 | ), | ||
| 449 | 3 => ( | ||
| 450 | u64::from(u32::from_le_bytes(fields.read()?)), | ||
| 451 | u64::from(u32::MAX), | ||
| 452 | 3, | ||
| 453 | ), | ||
| 454 | _ => unreachable!(), | ||
| 455 | }; | ||
| 456 | let cb = match (raw >> 25) & 3 { | ||
| 457 | 0 => u64::from(u32::from_le_bytes(fields.read()?)), | ||
| 458 | 1 => u64::from_le_bytes(fields.read()?), | ||
| 459 | 2 => u64::from(fields.read::<1>()?[0]) * 8, | ||
| 460 | 3 => u64::from(u16::from_le_bytes(fields.read()?)) * 8, | ||
| 461 | _ => unreachable!(), | ||
| 462 | }; | ||
| 463 | let reference = Chunk { | ||
| 464 | offset: if cb == 0 && stp == nil { | ||
| 465 | u64::MAX | ||
| 466 | } else { | ||
| 467 | stp << shift | ||
| 468 | }, | ||
| 469 | length: cb, | ||
| 470 | }; | ||
| 471 | if base == 1 { | ||
| 472 | if !reference.absent() { | ||
| 473 | reference.range(data)?; | ||
| 474 | } | ||
| 475 | Some(Reference::Data(reference)) | ||
| 476 | } else { | ||
| 477 | Some(Reference::NodeList(reference)) | ||
| 478 | } | ||
| 479 | } | ||
| 480 | _ => { | ||
| 481 | return Err(Error { | ||
| 482 | offset, | ||
| 483 | message: "Unsupported file-node base type", | ||
| 484 | }); | ||
| 485 | } | ||
| 486 | }; | ||
| 487 | nodes.push(Node { | ||
| 488 | id, | ||
| 489 | offset, | ||
| 490 | payload: fields.bytes, | ||
| 491 | reference, | ||
| 492 | }); | ||
| 493 | } | 555 | } |
| 556 | let next = fragment.next; | ||
| 557 | fragment.nodes(required, &mut nodes, |reference| { | ||
| 558 | reference.range(data).map(drop) | ||
| 559 | })?; | ||
| 494 | fragments.push(chunk); | 560 | fragments.push(chunk); |
| 495 | if nodes.len() == required { | 561 | if nodes.len() == required { |
| 496 | break; | 562 | break; |
| ... | @@ -517,6 +583,90 @@ impl<'a> Store<'a> { | ... | @@ -517,6 +583,90 @@ impl<'a> Store<'a> { |
| 517 | }) | 583 | }) |
| 518 | } | 584 | } |
| 519 | 585 | ||
| 586 | /// Where the next transaction appends. | ||
| 587 | pub(crate) fn state(&self) -> Result<StoreState> { | ||
| 588 | let file_type = self.header.file_type; | ||
| 589 | let mut log_crc = if file_type == FileType::Section { | ||
| 590 | u32::MAX | ||
| 591 | } else { | ||
| 592 | 0 | ||
| 593 | }; | ||
| 594 | for fragment in &self.transaction_fragments { | ||
| 595 | log_crc = transaction_crc( | ||
| 596 | log_crc, | ||
| 597 | fragment.entries, | ||
| 598 | file_type, | ||
| 599 | fragment.entries.len() + 20 > fragment.chunk.length as usize, | ||
| 600 | ); | ||
| 601 | } | ||
| 602 | let last = self.transaction_fragments.last().unwrap(); | ||
| 603 | let root = self.list(self.header.root)?; | ||
| 604 | let files = match root.nodes.iter().find(|node| node.id == 0x90) { | ||
| 605 | Some(node) => { | ||
| 606 | let Some(Reference::NodeList(chunk)) = node.reference else { | ||
| 607 | return Err(Error { | ||
| 608 | offset: node.offset, | ||
| 609 | message: "File-data store reference lacks a list", | ||
| 610 | }); | ||
| 611 | }; | ||
| 612 | Some(self.tail(chunk)?) | ||
| 613 | } | ||
| 614 | None => None, | ||
| 615 | }; | ||
| 616 | let mut spaces = BTreeMap::new(); | ||
| 617 | for node in root.nodes.iter().filter(|node| node.id == 8) { | ||
| 618 | let revisions = node | ||
| 619 | .referenced_list(self)? | ||
| 620 | .iter() | ||
| 621 | .rfind(|node| node.id == 0x10); | ||
| 622 | if let Some(Node { | ||
| 623 | reference: Some(Reference::NodeList(chunk)), | ||
| 624 | .. | ||
| 625 | }) = revisions | ||
| 626 | { | ||
| 627 | spaces.insert(node.fields(self).exguid()?, self.tail(*chunk)?); | ||
| 628 | } | ||
| 629 | } | ||
| 630 | Ok(StoreState { | ||
| 631 | stamp: crate::Stamp::of(self.data)?, | ||
| 632 | file_type, | ||
| 633 | log: (last.chunk, last.entries.len()), | ||
| 634 | log_crc, | ||
| 635 | max_list: self | ||
| 636 | .transaction_fragments | ||
| 637 | .iter() | ||
| 638 | .flat_map(|fragment| fragment.entries.chunks_exact(8)) | ||
| 639 | .map(|entry| u32::from_le_bytes(entry[..4].try_into().unwrap())) | ||
| 640 | .max() | ||
| 641 | .unwrap(), | ||
| 642 | root: self.tail(self.header.root)?, | ||
| 643 | files, | ||
| 644 | spaces, | ||
| 645 | }) | ||
| 646 | } | ||
| 647 | |||
| 648 | fn tail(&self, chunk: Chunk) -> Result<ListTail> { | ||
| 649 | let list = self.list(chunk)?; | ||
| 650 | let last = *list.fragments.last().unwrap(); | ||
| 651 | let start = usize::try_from(last.offset).unwrap(); | ||
| 652 | let node = list.nodes.last().ok_or(Error { | ||
| 653 | offset: start, | ||
| 654 | message: "Cannot append to an empty file-node list", | ||
| 655 | })?; | ||
| 656 | let header = | ||
| 657 | u32::from_le_bytes(self.data[node.offset..node.offset + 4].try_into().unwrap()); | ||
| 658 | Ok(ListTail { | ||
| 659 | id: u32::from_le_bytes(self.data[start + 8..start + 12].try_into().unwrap()), | ||
| 660 | fragments: u32::try_from(list.fragments.len()).map_err(|_| Error { | ||
| 661 | offset: start, | ||
| 662 | message: "File-node fragment sequences are exhausted", | ||
| 663 | })?, | ||
| 664 | last, | ||
| 665 | nodes: list.nodes.len(), | ||
| 666 | end: (node.offset + usize::try_from((header >> 10) & 0x1fff).unwrap()) as u64, | ||
| 667 | }) | ||
| 668 | } | ||
| 669 | |||
| 520 | pub fn chunk_data(&self, chunk: Chunk) -> Result<&'a [u8]> { | 670 | pub fn chunk_data(&self, chunk: Chunk) -> Result<&'a [u8]> { |
| 521 | Ok(&self.data[chunk.range(self.data)?]) | 671 | Ok(&self.data[chunk.range(self.data)?]) |
| 522 | } | 672 | } |
crates/onestore/src/write.rs+879-291| ... | @@ -1,7 +1,8 @@ | ... | @@ -1,7 +1,8 @@ |
| 1 | use crate::{ | 1 | use crate::{ |
| 2 | Chunk, Error, ExGuid, FileType, ObjectData, PropertySets, Reference, RevisionIndex, Store, | 2 | Chunk, Error, ExGuid, FileType, Node, ObjectData, PropertySets, Reference, RevisionIndex, |
| 3 | Value, | 3 | Store, Value, |
| 4 | store::{crc, transaction_crc}, | 4 | bytes::Cursor, |
| 5 | store::{Fragment, ListTail, StoreState, crc, transaction_crc}, | ||
| 5 | }; | 6 | }; |
| 6 | 7 | ||
| 7 | use std::{ | 8 | use std::{ |
| ... | @@ -14,12 +15,25 @@ mod tests; | ... | @@ -14,12 +15,25 @@ mod tests; |
| 14 | 15 | ||
| 15 | type Result<T> = std::result::Result<T, Error>; | 16 | type Result<T> = std::result::Result<T, Error>; |
| 16 | 17 | ||
| 18 | #[cfg(test)] | ||
| 19 | thread_local! { | ||
| 20 | /// When set, `fresh_guid` counts from it instead of drawing randomness, so tests can | ||
| 21 | /// compare builds byte for byte. | ||
| 22 | pub(crate) static GUIDS: std::cell::Cell<Option<u64>> = const { std::cell::Cell::new(None) }; | ||
| 23 | } | ||
| 24 | |||
| 17 | pub(crate) fn fresh_guid() -> Result<[u8; 16]> { | 25 | pub(crate) fn fresh_guid() -> Result<[u8; 16]> { |
| 18 | let mut guid = [0; 16]; | 26 | let mut guid = [0; 16]; |
| 19 | getrandom::fill(&mut guid).map_err(|_| Error { | 27 | getrandom::fill(&mut guid).map_err(|_| Error { |
| 20 | offset: 0, | 28 | offset: 0, |
| 21 | message: "System random source failed", | 29 | message: "System random source failed", |
| 22 | })?; | 30 | })?; |
| 31 | #[cfg(test)] | ||
| 32 | if let Some(next) = GUIDS.get() { | ||
| 33 | GUIDS.set(Some(next + 1)); | ||
| 34 | guid[..8].copy_from_slice(&next.to_le_bytes()); | ||
| 35 | guid[8..].copy_from_slice(&0x5eed_u64.to_le_bytes()); | ||
| 36 | } | ||
| 23 | guid[7] = (guid[7] & 0x0f) | 0x40; | 37 | guid[7] = (guid[7] & 0x0f) | 0x40; |
| 24 | guid[8] = (guid[8] & 0x3f) | 0x80; | 38 | guid[8] = (guid[8] & 0x3f) | 0x80; |
| 25 | Ok(guid) | 39 | Ok(guid) |
| ... | @@ -74,9 +88,14 @@ pub(crate) fn append(data: &mut Vec<u8>, bytes: &[u8]) -> Result<Chunk> { | ... | @@ -74,9 +88,14 @@ pub(crate) fn append(data: &mut Vec<u8>, bytes: &[u8]) -> Result<Chunk> { |
| 74 | } | 88 | } |
| 75 | 89 | ||
| 76 | pub(crate) fn append_list(data: &mut Vec<u8>, id: u32, nodes: &[Vec<u8>]) -> Result<Chunk> { | 90 | pub(crate) fn append_list(data: &mut Vec<u8>, id: u32, nodes: &[Vec<u8>]) -> Result<Chunk> { |
| 91 | append(data, &fragment(id, 0, nodes)) | ||
| 92 | } | ||
| 93 | |||
| 94 | /// The file-node list fragment `sequence` of list `id`, holding `nodes` and no successor. | ||
| 95 | fn fragment(id: u32, sequence: u32, nodes: &[Vec<u8>]) -> Vec<u8> { | ||
| 77 | let mut bytes = 0xa4567ab1f5f7f4c4_u64.to_le_bytes().to_vec(); | 96 | let mut bytes = 0xa4567ab1f5f7f4c4_u64.to_le_bytes().to_vec(); |
| 78 | bytes.extend_from_slice(&id.to_le_bytes()); | 97 | bytes.extend_from_slice(&id.to_le_bytes()); |
| 79 | bytes.extend_from_slice(&0_u32.to_le_bytes()); | 98 | bytes.extend_from_slice(&sequence.to_le_bytes()); |
| 80 | for node in nodes { | 99 | for node in nodes { |
| 81 | bytes.extend_from_slice(node); | 100 | bytes.extend_from_slice(node); |
| 82 | } | 101 | } |
| ... | @@ -84,10 +103,10 @@ pub(crate) fn append_list(data: &mut Vec<u8>, id: u32, nodes: &[Vec<u8>]) -> Res | ... | @@ -84,10 +103,10 @@ pub(crate) fn append_list(data: &mut Vec<u8>, id: u32, nodes: &[Vec<u8>]) -> Res |
| 84 | bytes.extend_from_slice(&u64::MAX.to_le_bytes()); | 103 | bytes.extend_from_slice(&u64::MAX.to_le_bytes()); |
| 85 | bytes.extend_from_slice(&0_u32.to_le_bytes()); | 104 | bytes.extend_from_slice(&0_u32.to_le_bytes()); |
| 86 | bytes.extend_from_slice(&0x8bc215c38233ba4b_u64.to_le_bytes()); | 105 | bytes.extend_from_slice(&0x8bc215c38233ba4b_u64.to_le_bytes()); |
| 87 | append(data, &bytes) | 106 | bytes |
| 88 | } | 107 | } |
| 89 | 108 | ||
| 90 | fn compact(id: ExGuid, table: &BTreeMap<u32, [u8; 16]>) -> Result<[u8; 4]> { | 109 | pub(crate) fn compact(id: ExGuid, table: &BTreeMap<u32, [u8; 16]>) -> Result<[u8; 4]> { |
| 91 | let index = table | 110 | let index = table |
| 92 | .iter() | 111 | .iter() |
| 93 | .find_map(|(index, guid)| (*guid == id.guid).then_some(*index)) | 112 | .find_map(|(index, guid)| (*guid == id.guid).then_some(*index)) |
| ... | @@ -378,6 +397,56 @@ pub(crate) fn patched( | ... | @@ -378,6 +397,56 @@ pub(crate) fn patched( |
| 378 | Ok(changed) | 397 | Ok(changed) |
| 379 | } | 398 | } |
| 380 | 399 | ||
| 400 | /// Whether `after` stores as `before` does: the same type and bytes, naming the same | ||
| 401 | /// identities. Stored tables keep only the entries an object names, so those decide. | ||
| 402 | pub(crate) fn unchanged(before: &crate::Object<'_>, after: &crate::Object<'_>) -> Result<bool> { | ||
| 403 | if before.jcid != after.jcid || before.data != after.data { | ||
| 404 | return Ok(false); | ||
| 405 | } | ||
| 406 | let entries = match after.data { | ||
| 407 | ObjectData::Properties(bytes) => table_entries(bytes)?, | ||
| 408 | _ => Vec::new(), | ||
| 409 | }; | ||
| 410 | Ok(entries | ||
| 411 | .iter() | ||
| 412 | .all(|entry| before.global_ids.get(entry) == after.global_ids.get(entry))) | ||
| 413 | } | ||
| 414 | |||
| 415 | /// `object` as a replacement a revision stores under `id`. | ||
| 416 | pub(crate) fn replacement(id: ExGuid, object: &crate::Object<'_>) -> Result<PropertyObject> { | ||
| 417 | let mut replacement = match object.data { | ||
| 418 | ObjectData::Properties(_) => PropertyObject::from_object(object)?, | ||
| 419 | ObjectData::File { | ||
| 420 | reference, | ||
| 421 | extension, | ||
| 422 | } => { | ||
| 423 | let text = |bytes: &[u8]| { | ||
| 424 | String::from_utf16( | ||
| 425 | &bytes | ||
| 426 | .chunks_exact(2) | ||
| 427 | .map(|pair| u16::from_le_bytes([pair[0], pair[1]])) | ||
| 428 | .collect::<Vec<_>>(), | ||
| 429 | ) | ||
| 430 | .map_err(|_| Error { | ||
| 431 | offset: 0, | ||
| 432 | message: "Invalid UTF-16 file-data declaration", | ||
| 433 | }) | ||
| 434 | }; | ||
| 435 | let mut replacement = PropertyObject::file(id, &text(reference)?, &text(extension)?)?; | ||
| 436 | replacement.jcid = object.jcid; | ||
| 437 | replacement | ||
| 438 | } | ||
| 439 | ObjectData::Encrypted(_) => { | ||
| 440 | return Err(Error { | ||
| 441 | offset: 0, | ||
| 442 | message: "Page edits only produce property objects", | ||
| 443 | }); | ||
| 444 | } | ||
| 445 | }; | ||
| 446 | replacement.reference(id)?; | ||
| 447 | Ok(replacement) | ||
| 448 | } | ||
| 449 | |||
| 381 | /// The object type OneNote gives embedded picture payload declarations; embedded files | 450 | /// The object type OneNote gives embedded picture payload declarations; embedded files |
| 382 | /// use `EMBEDDED_FILE_JCID` with the same declaration shape. | 451 | /// use `EMBEDDED_FILE_JCID` with the same declaration shape. |
| 383 | pub(crate) const FILE_DATA_JCID: u32 = 0x80039; | 452 | pub(crate) const FILE_DATA_JCID: u32 = 0x80039; |
| ... | @@ -784,6 +853,7 @@ fn used_entries<'o>( | ... | @@ -784,6 +853,7 @@ fn used_entries<'o>( |
| 784 | } | 853 | } |
| 785 | 854 | ||
| 786 | /// A space's revision as appending a revision and reading it back leaves it. | 855 | /// A space's revision as appending a revision and reading it back leaves it. |
| 856 | #[derive(Clone)] | ||
| 787 | pub(crate) struct LiveRevision<'a> { | 857 | pub(crate) struct LiveRevision<'a> { |
| 788 | pub revision: crate::ResolvedRevision<'a>, | 858 | pub revision: crate::ResolvedRevision<'a>, |
| 789 | /// Incoming references of each object reachable from the roots; a root counts once. | 859 | /// Incoming references of each object reachable from the roots; a root counts once. |
| ... | @@ -792,7 +862,7 @@ pub(crate) struct LiveRevision<'a> { | ... | @@ -792,7 +862,7 @@ pub(crate) struct LiveRevision<'a> { |
| 792 | /// objects alias. | 862 | /// objects alias. |
| 793 | readonly: BTreeMap<u32, BTreeMap<&'a [u8], BTreeSet<ExGuid>>>, | 863 | readonly: BTreeMap<u32, BTreeMap<&'a [u8], BTreeSet<ExGuid>>>, |
| 794 | /// Revisions in the dependency chain, counted to 512; zero before the first. | 864 | /// Revisions in the dependency chain, counted to 512; zero before the first. |
| 795 | depth: usize, | 865 | pub depth: usize, |
| 796 | } | 866 | } |
| 797 | 867 | ||
| 798 | impl<'a> LiveRevision<'a> { | 868 | impl<'a> LiveRevision<'a> { |
| ... | @@ -1230,39 +1300,6 @@ fn update( | ... | @@ -1230,39 +1300,6 @@ fn update( |
| 1230 | } | 1300 | } |
| 1231 | } | 1301 | } |
| 1232 | 1302 | ||
| 1233 | fn append_fragment( | ||
| 1234 | source: &[u8], | ||
| 1235 | output: &mut Vec<u8>, | ||
| 1236 | list: &crate::NodeList, | ||
| 1237 | nodes: &[Vec<u8>], | ||
| 1238 | ) -> Result<(u32, usize)> { | ||
| 1239 | let last_fragment = *list.fragments.last().unwrap(); | ||
| 1240 | let list_start = usize::try_from(last_fragment.offset).unwrap(); | ||
| 1241 | let list_id = u32::from_le_bytes(source[list_start + 8..list_start + 12].try_into().unwrap()); | ||
| 1242 | let chunk = append_list(output, list_id, nodes)?; | ||
| 1243 | let start = usize::try_from(chunk.offset).unwrap(); | ||
| 1244 | let sequence = u32::try_from(list.fragments.len()).map_err(|_| Error { | ||
| 1245 | offset: list_start, | ||
| 1246 | message: "File-node fragment sequences are exhausted", | ||
| 1247 | })?; | ||
| 1248 | output[start + 12..start + 16].copy_from_slice(&sequence.to_le_bytes()); | ||
| 1249 | let last_node = list.nodes.last().unwrap(); | ||
| 1250 | let node_header = u32::from_le_bytes( | ||
| 1251 | source[last_node.offset..last_node.offset + 4] | ||
| 1252 | .try_into() | ||
| 1253 | .unwrap(), | ||
| 1254 | ); | ||
| 1255 | let nodes_end = last_node.offset + usize::try_from((node_header >> 10) & 0x1fff).unwrap(); | ||
| 1256 | let tail = list_start + usize::try_from(last_fragment.length).unwrap() - 20; | ||
| 1257 | if tail - nodes_end >= 4 { | ||
| 1258 | output[nodes_end..nodes_end + 4].copy_from_slice(&node(0xff, None, &[])?); | ||
| 1259 | } | ||
| 1260 | output[tail..tail + 8].copy_from_slice(&chunk.offset.to_le_bytes()); | ||
| 1261 | output[tail + 8..tail + 12].copy_from_slice(&(chunk.length as u32).to_le_bytes()); | ||
| 1262 | |||
| 1263 | Ok((list_id, list.nodes.len() + nodes.len())) | ||
| 1264 | } | ||
| 1265 | |||
| 1266 | pub(crate) fn write_revisions( | 1303 | pub(crate) fn write_revisions( |
| 1267 | source: &[u8], | 1304 | source: &[u8], |
| 1268 | edit: impl FnOnce(&RevisionIndex<'_>) -> Result<BTreeMap<ExGuid, RevisionEdit>>, | 1305 | edit: impl FnOnce(&RevisionIndex<'_>) -> Result<BTreeMap<ExGuid, RevisionEdit>>, |
| ... | @@ -1372,36 +1409,7 @@ pub(crate) fn build_on( | ... | @@ -1372,36 +1409,7 @@ pub(crate) fn build_on( |
| 1372 | None => index.resolve(space, rid), | 1409 | None => index.resolve(space, rid), |
| 1373 | }; | 1410 | }; |
| 1374 | let changes = edit(index)?; | 1411 | let changes = edit(index)?; |
| 1375 | // Room for the revision, so appending does not double the image. | 1412 | let mut appending = Appending::new(store.state()?); |
| 1376 | let mut output = Vec::with_capacity(source.len() + source.len() / 16 + (1 << 16)); | ||
| 1377 | output.extend_from_slice(source); | ||
| 1378 | // Native files reserve 1 KiB per transaction-log fragment; a fragment that ends the file | ||
| 1379 | // keeps that room before the first new chunk. | ||
| 1380 | let tail = store.transaction_fragments.last().unwrap().chunk; | ||
| 1381 | let reserved = usize::try_from(tail.offset + tail.length.max(1024) + 32).unwrap(); | ||
| 1382 | if usize::try_from(tail.offset + tail.length) | ||
| 1383 | .unwrap() | ||
| 1384 | .next_multiple_of(8) | ||
| 1385 | >= output.len() | ||
| 1386 | { | ||
| 1387 | output.resize(reserved, 0); | ||
| 1388 | } | ||
| 1389 | let mut maximum = store | ||
| 1390 | .transaction_fragments | ||
| 1391 | .iter() | ||
| 1392 | .flat_map(|fragment| fragment.entries.chunks_exact(8)) | ||
| 1393 | .map(|entry| u32::from_le_bytes(entry[..4].try_into().unwrap())) | ||
| 1394 | .max() | ||
| 1395 | .unwrap(); | ||
| 1396 | let mut allocate_list = || { | ||
| 1397 | maximum = maximum.checked_add(1).ok_or(Error { | ||
| 1398 | offset: 0, | ||
| 1399 | message: "File-node list identities are exhausted", | ||
| 1400 | })?; | ||
| 1401 | Ok::<_, Error>(maximum) | ||
| 1402 | }; | ||
| 1403 | let mut counts = Vec::new(); | ||
| 1404 | let mut root_nodes = Vec::new(); | ||
| 1405 | for (space, change) in changes { | 1413 | for (space, change) in changes { |
| 1406 | let new_space = matches!(change, RevisionEdit::Create { .. }); | 1414 | let new_space = matches!(change, RevisionEdit::Create { .. }); |
| 1407 | let current = (ExGuid::default(), 1_u32); | 1415 | let current = (ExGuid::default(), 1_u32); |
| ... | @@ -1457,18 +1465,19 @@ pub(crate) fn build_on( | ... | @@ -1457,18 +1465,19 @@ pub(crate) fn build_on( |
| 1457 | ) | 1465 | ) |
| 1458 | } | 1466 | } |
| 1459 | }; | 1467 | }; |
| 1460 | let existing: BTreeSet<ExGuid> = revision.objects.keys().copied().collect(); | ||
| 1461 | let depth = rid.map_or(0, |rid| chain_depth(index, space, rid)); | 1468 | let depth = rid.map_or(0, |rid| chain_depth(index, space, rid)); |
| 1462 | let mut live = LiveRevision::new(revision, depth)?; | 1469 | let mut live = LiveRevision::new(revision, depth)?; |
| 1463 | let replacements = live.prepare(replacements, is_section)?; | 1470 | let replacements = live.prepare(replacements, is_section)?; |
| 1464 | if replacements.is_empty() { | 1471 | if replacements.is_empty() { |
| 1465 | continue; | 1472 | continue; |
| 1466 | } | 1473 | } |
| 1467 | let Commit { | 1474 | let replaced: BTreeSet<ExGuid> = replacements.keys().copied().collect(); |
| 1468 | checkpoint, | 1475 | let created = replaced |
| 1469 | changed, | 1476 | .iter() |
| 1470 | .. | 1477 | .filter(|id| !live.revision.objects.contains_key(id)) |
| 1471 | } = live.commit( | 1478 | .copied() |
| 1479 | .collect(); | ||
| 1480 | let commit = live.commit( | ||
| 1472 | replacements | 1481 | replacements |
| 1473 | .iter() | 1482 | .iter() |
| 1474 | .map(|(id, replacement)| { | 1483 | .map(|(id, replacement)| { |
| ... | @@ -1480,12 +1489,224 @@ pub(crate) fn build_on( | ... | @@ -1480,12 +1489,224 @@ pub(crate) fn build_on( |
| 1480 | }) | 1489 | }) |
| 1481 | .collect::<Result<Vec<_>>>()?, | 1490 | .collect::<Result<Vec<_>>>()?, |
| 1482 | )?; | 1491 | )?; |
| 1492 | // A dependent revision inherits its key, as OneNote writes it. | ||
| 1493 | let key = if protection.is_some() && commit.checkpoint { | ||
| 1494 | Some( | ||
| 1495 | index | ||
| 1496 | .spaces | ||
| 1497 | .get(&space) | ||
| 1498 | .and_then(|space| { | ||
| 1499 | space.revisions.values().find_map(|revision| { | ||
| 1500 | revision.nodes.first().filter(|node| node.id == 0x7c) | ||
| 1501 | }) | ||
| 1502 | }) | ||
| 1503 | .ok_or(Error { | ||
| 1504 | offset: 0, | ||
| 1505 | message: "Protected revisions continue a protected object space", | ||
| 1506 | })?, | ||
| 1507 | ) | ||
| 1508 | } else { | ||
| 1509 | None | ||
| 1510 | }; | ||
| 1511 | appending.revision( | ||
| 1512 | &Sealing { | ||
| 1513 | space, | ||
| 1514 | previous: rid, | ||
| 1515 | new_space, | ||
| 1516 | label, | ||
| 1517 | live: &live, | ||
| 1518 | commit: &commit, | ||
| 1519 | replaced: &replaced, | ||
| 1520 | created: &created, | ||
| 1521 | }, | ||
| 1522 | &[(0, source)], | ||
| 1523 | protection, | ||
| 1524 | key, | ||
| 1525 | )?; | ||
| 1526 | } | ||
| 1527 | appending.payloads(payloads, protection)?; | ||
| 1528 | Ok(match appending.finish()? { | ||
| 1529 | Some((transaction, _)) => { | ||
| 1530 | let mut output = source.to_vec(); | ||
| 1531 | transaction.apply(&mut output)?; | ||
| 1532 | output | ||
| 1533 | } | ||
| 1534 | None => source.to_vec(), | ||
| 1535 | }) | ||
| 1536 | } | ||
| 1537 | |||
| 1538 | /// A space's revision as `LiveRevision::commit` left it, ready to append. | ||
| 1539 | pub(crate) struct Sealing<'r, 'a> { | ||
| 1540 | pub space: ExGuid, | ||
| 1541 | /// The revision it depends on unless it is a checkpoint; none for a new space or label. | ||
| 1542 | pub previous: Option<ExGuid>, | ||
| 1543 | pub new_space: bool, | ||
| 1544 | /// The context and role it is current under. | ||
| 1545 | pub label: (ExGuid, u32), | ||
| 1546 | pub live: &'r LiveRevision<'a>, | ||
| 1547 | pub commit: &'r Commit, | ||
| 1548 | /// Objects whose bytes the revision stores rather than referencing stored bytes. | ||
| 1549 | pub replaced: &'r BTreeSet<ExGuid>, | ||
| 1550 | /// Replaced objects the space did not hold before. | ||
| 1551 | pub created: &'r BTreeSet<ExGuid>, | ||
| 1552 | } | ||
| 1553 | |||
| 1554 | /// The chunk `bytes` occupies in a store whose bytes `segments` hold at their offsets. | ||
| 1555 | fn located(segments: &[(u64, &[u8])], bytes: &[u8]) -> Result<Chunk> { | ||
| 1556 | let address = bytes.as_ptr().addr(); | ||
| 1557 | segments | ||
| 1558 | .iter() | ||
| 1559 | .find_map(|(offset, segment)| { | ||
| 1560 | let start = address.checked_sub(segment.as_ptr().addr())?; | ||
| 1561 | (start + bytes.len() <= segment.len()).then(|| Chunk { | ||
| 1562 | offset: offset + start as u64, | ||
| 1563 | length: bytes.len() as u64, | ||
| 1564 | }) | ||
| 1565 | }) | ||
| 1566 | .ok_or(Error { | ||
| 1567 | offset: 0, | ||
| 1568 | message: "Stored object data lies outside the store", | ||
| 1569 | }) | ||
| 1570 | } | ||
| 1571 | |||
| 1572 | /// A transaction under construction: bytes appended at the end of a store and patches | ||
| 1573 | /// inside it, advancing `state` as its lists grow. | ||
| 1574 | pub(crate) struct Appending { | ||
| 1575 | pub state: StoreState, | ||
| 1576 | base: crate::Stamp, | ||
| 1577 | append: Vec<u8>, | ||
| 1578 | patches: Vec<(u64, Vec<u8>)>, | ||
| 1579 | /// File-node counts of the lists this transaction writes, as log entries. | ||
| 1580 | counts: Vec<(u32, usize)>, | ||
| 1581 | /// Nodes the root list gains: new object spaces and the file-data store. | ||
| 1582 | root_nodes: Vec<Vec<u8>>, | ||
| 1583 | } | ||
| 1584 | |||
| 1585 | impl Appending { | ||
| 1586 | pub(crate) fn new(state: StoreState) -> Self { | ||
| 1587 | let base = state.stamp.clone(); | ||
| 1588 | let mut append = Vec::with_capacity(1 << 16); | ||
| 1589 | // Native files reserve 1 KiB per transaction-log fragment; a fragment that ends the | ||
| 1590 | // file keeps that room before the first new chunk. | ||
| 1591 | let (tail, _) = state.log; | ||
| 1592 | if (tail.offset + tail.length).next_multiple_of(8) >= base.length { | ||
| 1593 | let reserved = tail.offset + tail.length.max(1024) + 32; | ||
| 1594 | append.resize(reserved.saturating_sub(base.length) as usize, 0); | ||
| 1595 | } | ||
| 1596 | Self { | ||
| 1597 | state, | ||
| 1598 | base, | ||
| 1599 | append, | ||
| 1600 | patches: Vec::new(), | ||
| 1601 | counts: Vec::new(), | ||
| 1602 | root_nodes: Vec::new(), | ||
| 1603 | } | ||
| 1604 | } | ||
| 1605 | |||
| 1606 | fn end(&self) -> u64 { | ||
| 1607 | self.base.length + self.append.len() as u64 | ||
| 1608 | } | ||
| 1609 | |||
| 1610 | fn append(&mut self, bytes: &[u8]) -> Result<Chunk> { | ||
| 1611 | let length = u64::from(u32::try_from(bytes.len()).map_err(|_| Error { | ||
| 1612 | offset: 0, | ||
| 1613 | message: "Chunk exceeds the encoded length limit", | ||
| 1614 | })?); | ||
| 1615 | let padding = self.end().next_multiple_of(8) - self.end(); | ||
| 1616 | self.append.resize(self.append.len() + padding as usize, 0); | ||
| 1617 | let offset = self.end(); | ||
| 1618 | self.append.extend_from_slice(bytes); | ||
| 1619 | Ok(Chunk { offset, length }) | ||
| 1620 | } | ||
| 1621 | |||
| 1622 | /// Writes `bytes` at a file offset: into the appended bytes, or as a patch. | ||
| 1623 | fn write(&mut self, offset: u64, bytes: &[u8]) { | ||
| 1624 | match offset.checked_sub(self.base.length) { | ||
| 1625 | Some(at) => { | ||
| 1626 | let at = at as usize; | ||
| 1627 | self.append[at..at + bytes.len()].copy_from_slice(bytes); | ||
| 1628 | } | ||
| 1629 | None => self.patches.push((offset, bytes.to_vec())), | ||
| 1630 | } | ||
| 1631 | } | ||
| 1632 | |||
| 1633 | fn allocate_list(&mut self) -> Result<u32> { | ||
| 1634 | self.state.max_list = self.state.max_list.checked_add(1).ok_or(Error { | ||
| 1635 | offset: 0, | ||
| 1636 | message: "File-node list identities are exhausted", | ||
| 1637 | })?; | ||
| 1638 | Ok(self.state.max_list) | ||
| 1639 | } | ||
| 1640 | |||
| 1641 | /// Appends `nodes` as a new list. | ||
| 1642 | fn list(&mut self, nodes: &[Vec<u8>]) -> Result<(Chunk, ListTail)> { | ||
| 1643 | let id = self.allocate_list()?; | ||
| 1644 | let chunk = self.append(&fragment(id, 0, nodes))?; | ||
| 1645 | self.counts.push((id, nodes.len())); | ||
| 1646 | Ok(( | ||
| 1647 | chunk, | ||
| 1648 | ListTail { | ||
| 1649 | id, | ||
| 1650 | fragments: 1, | ||
| 1651 | last: chunk, | ||
| 1652 | nodes: nodes.len(), | ||
| 1653 | end: chunk.offset + 16 + nodes.iter().map(Vec::len).sum::<usize>() as u64, | ||
| 1654 | }, | ||
| 1655 | )) | ||
| 1656 | } | ||
| 1657 | |||
| 1658 | /// Appends `nodes` to the list ending at `tail` as its next fragment. | ||
| 1659 | fn extend(&mut self, tail: &mut ListTail, nodes: &[Vec<u8>]) -> Result<()> { | ||
| 1660 | let chunk = self.append(&fragment(tail.id, tail.fragments, nodes))?; | ||
| 1661 | let next = tail.last.offset + tail.last.length - 20; | ||
| 1662 | if next - tail.end >= 4 { | ||
| 1663 | self.write(tail.end, &node(0xff, None, &[])?); | ||
| 1664 | } | ||
| 1665 | let mut reference = chunk.offset.to_le_bytes().to_vec(); | ||
| 1666 | reference.extend_from_slice(&(chunk.length as u32).to_le_bytes()); | ||
| 1667 | self.write(next, &reference); | ||
| 1668 | *tail = ListTail { | ||
| 1669 | id: tail.id, | ||
| 1670 | fragments: tail.fragments.checked_add(1).ok_or(Error { | ||
| 1671 | offset: 0, | ||
| 1672 | message: "File-node fragment sequences are exhausted", | ||
| 1673 | })?, | ||
| 1674 | last: chunk, | ||
| 1675 | nodes: tail.nodes + nodes.len(), | ||
| 1676 | end: chunk.offset + 16 + nodes.iter().map(Vec::len).sum::<usize>() as u64, | ||
| 1677 | }; | ||
| 1678 | self.counts.push((tail.id, tail.nodes)); | ||
| 1679 | Ok(()) | ||
| 1680 | } | ||
| 1681 | |||
| 1682 | /// Appends a space's next revision: its manifest, object groups and new data. Returns the | ||
| 1683 | /// revision's identity and where it stored each replaced object's bytes. | ||
| 1684 | pub(crate) fn revision( | ||
| 1685 | &mut self, | ||
| 1686 | sealing: &Sealing<'_, '_>, | ||
| 1687 | segments: &[(u64, &[u8])], | ||
| 1688 | protection: Option<&dyn Protection>, | ||
| 1689 | key: Option<&crate::Node<'_>>, | ||
| 1690 | ) -> Result<(ExGuid, Vec<(ExGuid, Chunk)>)> { | ||
| 1691 | let is_section = self.state.file_type == FileType::Section; | ||
| 1692 | let Sealing { | ||
| 1693 | space, | ||
| 1694 | label, | ||
| 1695 | live, | ||
| 1696 | commit, | ||
| 1697 | .. | ||
| 1698 | } = *sealing; | ||
| 1483 | let revision = &live.revision; | 1699 | let revision = &live.revision; |
| 1484 | let selected: Vec<_> = revision | 1700 | let checkpoint = commit.checkpoint; |
| 1485 | .objects | 1701 | let selected: Vec<(&ExGuid, &crate::Object<'_>)> = if checkpoint { |
| 1486 | .iter() | 1702 | revision.objects.iter().collect() |
| 1487 | .filter(|(id, _)| checkpoint || changed.contains(id)) | 1703 | } else { |
| 1488 | .collect(); | 1704 | commit |
| 1705 | .changed | ||
| 1706 | .iter() | ||
| 1707 | .map(|id| (id, &revision.objects[id])) | ||
| 1708 | .collect() | ||
| 1709 | }; | ||
| 1489 | // A table-of-contents manifest has one global id table (sections group objects, | 1710 | // A table-of-contents manifest has one global id table (sections group objects, |
| 1490 | // each group with its own table); OneNote resolves every node against it. | 1711 | // each group with its own table); OneNote resolves every node against it. |
| 1491 | let toc_table = if is_section { | 1712 | let toc_table = if is_section { |
| ... | @@ -1535,7 +1756,10 @@ pub(crate) fn build_on( | ... | @@ -1535,7 +1756,10 @@ pub(crate) fn build_on( |
| 1535 | if checkpoint { | 1756 | if checkpoint { |
| 1536 | ExGuid::default() | 1757 | ExGuid::default() |
| 1537 | } else { | 1758 | } else { |
| 1538 | rid.unwrap() | 1759 | sealing.previous.ok_or(Error { |
| 1760 | offset: 0, | ||
| 1761 | message: "A dependent revision needs the revision it follows", | ||
| 1762 | })? | ||
| 1539 | } | 1763 | } |
| 1540 | .encode(&mut start); | 1764 | .encode(&mut start); |
| 1541 | if !is_section { | 1765 | if !is_section { |
| ... | @@ -1548,7 +1772,7 @@ pub(crate) fn build_on( | ... | @@ -1548,7 +1772,7 @@ pub(crate) fn build_on( |
| 1548 | label.0.encode(&mut start); | 1772 | label.0.encode(&mut start); |
| 1549 | } | 1773 | } |
| 1550 | let mut manifest = Vec::new(); | 1774 | let mut manifest = Vec::new(); |
| 1551 | if new_space { | 1775 | if sealing.new_space { |
| 1552 | let mut payload = Vec::new(); | 1776 | let mut payload = Vec::new(); |
| 1553 | space.encode(&mut payload); | 1777 | space.encode(&mut payload); |
| 1554 | payload.extend_from_slice(&0_u32.to_le_bytes()); | 1778 | payload.extend_from_slice(&0_u32.to_le_bytes()); |
| ... | @@ -1563,23 +1787,14 @@ pub(crate) fn build_on( | ... | @@ -1563,23 +1787,14 @@ pub(crate) fn build_on( |
| 1563 | None, | 1787 | None, |
| 1564 | &start, | 1788 | &start, |
| 1565 | )?); | 1789 | )?); |
| 1566 | // A dependent revision inherits its key, as OneNote writes it. | ||
| 1567 | if protection.is_some() && checkpoint { | 1790 | if protection.is_some() && checkpoint { |
| 1568 | let key = index | 1791 | let key = key.ok_or(Error { |
| 1569 | .spaces | 1792 | offset: 0, |
| 1570 | .get(&space) | 1793 | message: "Protected revisions continue a protected object space", |
| 1571 | .and_then(|space| { | 1794 | })?; |
| 1572 | space | ||
| 1573 | .revisions | ||
| 1574 | .values() | ||
| 1575 | .find_map(|revision| revision.nodes.first().filter(|node| node.id == 0x7c)) | ||
| 1576 | }) | ||
| 1577 | .ok_or(Error { | ||
| 1578 | offset: 0, | ||
| 1579 | message: "Protected revisions continue a protected object space", | ||
| 1580 | })?; | ||
| 1581 | manifest.push(node(0x7c, key.reference, key.payload)?); | 1795 | manifest.push(node(0x7c, key.reference, key.payload)?); |
| 1582 | } | 1796 | } |
| 1797 | let mut stored = Vec::new(); | ||
| 1583 | for (table, objects) in groups { | 1798 | for (table, objects) in groups { |
| 1584 | let mut payload = Vec::new(); | 1799 | let mut payload = Vec::new(); |
| 1585 | let mut group = if is_section { | 1800 | let mut group = if is_section { |
| ... | @@ -1651,33 +1866,31 @@ pub(crate) fn build_on( | ... | @@ -1651,33 +1866,31 @@ pub(crate) fn build_on( |
| 1651 | } | 1866 | } |
| 1652 | } | 1867 | } |
| 1653 | } | 1868 | } |
| 1654 | append(&mut output, &mapped)? | 1869 | self.append(&mapped)? |
| 1655 | } else if replacements.contains_key(&id) { | 1870 | } else if sealing.replaced.contains(&id) { |
| 1656 | match protection { | 1871 | let chunk = match protection { |
| 1657 | Some(protection) => { | 1872 | Some(protection) => { |
| 1658 | append(&mut output, &protection.seal_property(bytes)?)? | 1873 | self.append(&protection.seal_property(bytes)?)? |
| 1659 | } | 1874 | } |
| 1660 | None => append(&mut output, bytes)?, | 1875 | None => self.append(bytes)?, |
| 1661 | } | ||
| 1662 | } else { | ||
| 1663 | let stored = match protection { | ||
| 1664 | Some(protection) => protection.stored(bytes).ok_or(Error { | ||
| 1665 | offset: 0, | ||
| 1666 | message: "Protected object has no stored form", | ||
| 1667 | })?, | ||
| 1668 | None => bytes, | ||
| 1669 | }; | 1876 | }; |
| 1670 | Chunk { | 1877 | stored.push((id, chunk)); |
| 1671 | offset: u64::try_from( | 1878 | chunk |
| 1672 | stored.as_ptr().addr() - source.as_ptr().addr(), | 1879 | } else { |
| 1673 | ) | 1880 | located( |
| 1674 | .unwrap(), | 1881 | segments, |
| 1675 | length: u64::try_from(stored.len()).unwrap(), | 1882 | match protection { |
| 1676 | } | 1883 | Some(protection) => protection.stored(bytes).ok_or(Error { |
| 1884 | offset: 0, | ||
| 1885 | message: "Protected object has no stored form", | ||
| 1886 | })?, | ||
| 1887 | None => bytes, | ||
| 1888 | }, | ||
| 1889 | )? | ||
| 1677 | }; | 1890 | }; |
| 1678 | // A table-of-contents object is declared when the revision is a | 1891 | // A table-of-contents object is declared when the revision is a |
| 1679 | // checkpoint or the object is new, and revised otherwise. | 1892 | // checkpoint or the object is new, and revised otherwise. |
| 1680 | let declared = checkpoint || !existing.contains(&id); | 1893 | let declared = checkpoint || sealing.created.contains(&id); |
| 1681 | if is_section { | 1894 | if is_section { |
| 1682 | declaration.extend_from_slice(&object.jcid.to_le_bytes()); | 1895 | declaration.extend_from_slice(&object.jcid.to_le_bytes()); |
| 1683 | declaration.push(flags); | 1896 | declaration.push(flags); |
| ... | @@ -1720,9 +1933,7 @@ pub(crate) fn build_on( | ... | @@ -1720,9 +1933,7 @@ pub(crate) fn build_on( |
| 1720 | } | 1933 | } |
| 1721 | if is_section { | 1934 | if is_section { |
| 1722 | group.push(node(0xb8, None, &[])?); | 1935 | group.push(node(0xb8, None, &[])?); |
| 1723 | let group_id = allocate_list()?; | 1936 | let (chunk, _) = self.list(&group)?; |
| 1724 | let chunk = append_list(&mut output, group_id, &group)?; | ||
| 1725 | counts.push((group_id, group.len())); | ||
| 1726 | manifest.push(node(0xb0, Some(Reference::NodeList(chunk)), &payload)?); | 1937 | manifest.push(node(0xb0, Some(Reference::NodeList(chunk)), &payload)?); |
| 1727 | let mut overrides = vec![0; 8]; | 1938 | let mut overrides = vec![0; 8]; |
| 1728 | overrides.extend_from_slice(&(!override_crc).to_le_bytes()); | 1939 | overrides.extend_from_slice(&(!override_crc).to_le_bytes()); |
| ... | @@ -1751,198 +1962,575 @@ pub(crate) fn build_on( | ... | @@ -1751,198 +1962,575 @@ pub(crate) fn build_on( |
| 1751 | } | 1962 | } |
| 1752 | } | 1963 | } |
| 1753 | manifest.push(node(0x1c, None, &[])?); | 1964 | manifest.push(node(0x1c, None, &[])?); |
| 1754 | if new_space { | 1965 | if sealing.new_space { |
| 1755 | let list_id = allocate_list()?; | 1966 | let (chunk, tail) = self.list(&manifest)?; |
| 1756 | let chunk = append_list(&mut output, list_id, &manifest)?; | ||
| 1757 | counts.push((list_id, manifest.len())); | ||
| 1758 | let mut payload = Vec::new(); | 1967 | let mut payload = Vec::new(); |
| 1759 | space.encode(&mut payload); | 1968 | space.encode(&mut payload); |
| 1760 | let nodes = vec![ | 1969 | let (chunk, _) = self.list(&[ |
| 1761 | node(0xc, None, &payload)?, | 1970 | node(0xc, None, &payload)?, |
| 1762 | node(0x10, Some(Reference::NodeList(chunk)), &[])?, | 1971 | node(0x10, Some(Reference::NodeList(chunk)), &[])?, |
| 1763 | ]; | 1972 | ])?; |
| 1764 | let list_id = allocate_list()?; | 1973 | self.root_nodes |
| 1765 | let chunk = append_list(&mut output, list_id, &nodes)?; | 1974 | .push(node(8, Some(Reference::NodeList(chunk)), &payload)?); |
| 1766 | counts.push((list_id, nodes.len())); | 1975 | self.state.spaces.insert(space, tail); |
| 1767 | root_nodes.push(node(8, Some(Reference::NodeList(chunk)), &payload)?); | ||
| 1768 | } else { | 1976 | } else { |
| 1769 | let root = store.list(store.header.root)?; | 1977 | let mut tail = *self.state.spaces.get(&space).ok_or(Error { |
| 1770 | let space_node = root | ||
| 1771 | .nodes | ||
| 1772 | .iter() | ||
| 1773 | .find(|node| node.id == 8 && node.fields(store).exguid() == Ok(space)) | ||
| 1774 | .ok_or(Error { | ||
| 1775 | offset: 0, | ||
| 1776 | message: "Object space is absent from the root list", | ||
| 1777 | })?; | ||
| 1778 | let space_list = space_node.referenced_list(store)?; | ||
| 1779 | let revision_node = space_list.iter().rfind(|node| node.id == 0x10).unwrap(); | ||
| 1780 | let Some(Reference::NodeList(manifest_reference)) = revision_node.reference else { | ||
| 1781 | unreachable!() | ||
| 1782 | }; | ||
| 1783 | let revision_list = store.list(manifest_reference)?; | ||
| 1784 | counts.push(append_fragment( | ||
| 1785 | source, | ||
| 1786 | &mut output, | ||
| 1787 | revision_list, | ||
| 1788 | &manifest, | ||
| 1789 | )?); | ||
| 1790 | } | ||
| 1791 | } | ||
| 1792 | let mut data_nodes = Vec::new(); | ||
| 1793 | for (guid, payload) in payloads { | ||
| 1794 | if !is_section { | ||
| 1795 | return Err(Error { | ||
| 1796 | offset: 0, | 1978 | offset: 0, |
| 1797 | message: "Embedded payloads require a section file", | 1979 | message: "Object space is absent from the root list", |
| 1798 | }); | 1980 | })?; |
| 1981 | self.extend(&mut tail, &manifest)?; | ||
| 1982 | self.state.spaces.insert(space, tail); | ||
| 1799 | } | 1983 | } |
| 1800 | let mut blob = vec![ | 1984 | Ok((new_rid, stored)) |
| 1801 | 0xe7, 0x16, 0xe3, 0xbd, 0x65, 0x26, 0x11, 0x45, 0xa4, 0xc4, 0x8d, 0x4d, 0x0b, 0x7a, | ||
| 1802 | 0x9e, 0xac, | ||
| 1803 | ]; | ||
| 1804 | let sealed = protection.map(|protection| protection.seal_file(payload)); | ||
| 1805 | let payload = sealed.as_deref().unwrap_or(*payload); | ||
| 1806 | blob.extend_from_slice(&(payload.len() as u64).to_le_bytes()); | ||
| 1807 | blob.extend_from_slice(&[0; 12]); | ||
| 1808 | blob.extend_from_slice(payload); | ||
| 1809 | blob.resize(blob.len().next_multiple_of(8), 0); | ||
| 1810 | blob.extend_from_slice(&[ | ||
| 1811 | 0x22, 0xa7, 0xfb, 0x71, 0x79, 0x0f, 0x0b, 0x4a, 0xbb, 0x13, 0x89, 0x92, 0x56, 0x42, | ||
| 1812 | 0x6b, 0x24, | ||
| 1813 | ]); | ||
| 1814 | let chunk = append(&mut output, &blob)?; | ||
| 1815 | data_nodes.push(node(0x94, Some(Reference::Data(chunk)), guid)?); | ||
| 1816 | } | 1985 | } |
| 1817 | if !data_nodes.is_empty() { | 1986 | |
| 1987 | /// Embeds payloads as file-data store objects referenced from the root file node list | ||
| 1988 | /// under their identities, as OneNote embeds pictures and attachments. | ||
| 1989 | pub(crate) fn payloads( | ||
| 1990 | &mut self, | ||
| 1991 | payloads: &[([u8; 16], &[u8])], | ||
| 1992 | protection: Option<&dyn Protection>, | ||
| 1993 | ) -> Result<()> { | ||
| 1994 | let mut data_nodes = Vec::new(); | ||
| 1995 | for (guid, payload) in payloads { | ||
| 1996 | if self.state.file_type != FileType::Section { | ||
| 1997 | return Err(Error { | ||
| 1998 | offset: 0, | ||
| 1999 | message: "Embedded payloads require a section file", | ||
| 2000 | }); | ||
| 2001 | } | ||
| 2002 | let mut blob = vec![ | ||
| 2003 | 0xe7, 0x16, 0xe3, 0xbd, 0x65, 0x26, 0x11, 0x45, 0xa4, 0xc4, 0x8d, 0x4d, 0x0b, 0x7a, | ||
| 2004 | 0x9e, 0xac, | ||
| 2005 | ]; | ||
| 2006 | let sealed = protection.map(|protection| protection.seal_file(payload)); | ||
| 2007 | let payload = sealed.as_deref().unwrap_or(*payload); | ||
| 2008 | blob.extend_from_slice(&(payload.len() as u64).to_le_bytes()); | ||
| 2009 | blob.extend_from_slice(&[0; 12]); | ||
| 2010 | blob.extend_from_slice(payload); | ||
| 2011 | blob.resize(blob.len().next_multiple_of(8), 0); | ||
| 2012 | blob.extend_from_slice(&[ | ||
| 2013 | 0x22, 0xa7, 0xfb, 0x71, 0x79, 0x0f, 0x0b, 0x4a, 0xbb, 0x13, 0x89, 0x92, 0x56, 0x42, | ||
| 2014 | 0x6b, 0x24, | ||
| 2015 | ]); | ||
| 2016 | let chunk = self.append(&blob)?; | ||
| 2017 | data_nodes.push(node(0x94, Some(Reference::Data(chunk)), guid)?); | ||
| 2018 | } | ||
| 2019 | if data_nodes.is_empty() { | ||
| 2020 | return Ok(()); | ||
| 2021 | } | ||
| 1818 | // Payload declarations live in the file-data store list the root list references. | 2022 | // Payload declarations live in the file-data store list the root list references. |
| 1819 | let root = store.list(store.header.root)?; | 2023 | match self.state.files { |
| 1820 | match root.nodes.iter().find(|node| node.id == 0x90) { | 2024 | Some(mut tail) => { |
| 1821 | Some(reference) => { | 2025 | self.extend(&mut tail, &data_nodes)?; |
| 1822 | let Some(Reference::NodeList(chunk)) = reference.reference else { | 2026 | self.state.files = Some(tail); |
| 1823 | return Err(Error { | ||
| 1824 | offset: reference.offset, | ||
| 1825 | message: "File-data store reference lacks a list", | ||
| 1826 | }); | ||
| 1827 | }; | ||
| 1828 | counts.push(append_fragment( | ||
| 1829 | source, | ||
| 1830 | &mut output, | ||
| 1831 | store.list(chunk)?, | ||
| 1832 | &data_nodes, | ||
| 1833 | )?); | ||
| 1834 | } | 2027 | } |
| 1835 | None => { | 2028 | None => { |
| 1836 | let list_id = allocate_list()?; | 2029 | let (chunk, tail) = self.list(&data_nodes)?; |
| 1837 | let chunk = append_list(&mut output, list_id, &data_nodes)?; | 2030 | self.state.files = Some(tail); |
| 1838 | counts.push((list_id, data_nodes.len())); | 2031 | self.root_nodes |
| 1839 | root_nodes.push(node(0x90, Some(Reference::NodeList(chunk)), &[])?); | 2032 | .push(node(0x90, Some(Reference::NodeList(chunk)), &[])?); |
| 1840 | } | 2033 | } |
| 1841 | } | 2034 | } |
| 2035 | Ok(()) | ||
| 1842 | } | 2036 | } |
| 1843 | if !root_nodes.is_empty() { | 2037 | |
| 1844 | counts.push(append_fragment( | 2038 | /// The transaction committing what was appended, and the state it leaves; none when |
| 1845 | source, | 2039 | /// nothing was. |
| 1846 | &mut output, | 2040 | pub(crate) fn finish(mut self) -> Result<Option<(crate::Transaction, StoreState)>> { |
| 1847 | store.list(store.header.root)?, | 2041 | if !self.root_nodes.is_empty() { |
| 1848 | &root_nodes, | 2042 | let nodes = std::mem::take(&mut self.root_nodes); |
| 1849 | )?); | 2043 | let mut root = self.state.root; |
| 2044 | self.extend(&mut root, &nodes)?; | ||
| 2045 | self.state.root = root; | ||
| 2046 | } | ||
| 2047 | if self.counts.is_empty() { | ||
| 2048 | return Ok(None); | ||
| 2049 | } | ||
| 2050 | let file_type = self.state.file_type; | ||
| 2051 | let is_section = file_type == FileType::Section; | ||
| 2052 | let mut header = self.base.header; | ||
| 2053 | let count = u32::from_le_bytes(header[96..100].try_into().unwrap()); | ||
| 2054 | let transactions = count.checked_add(1).ok_or(Error { | ||
| 2055 | offset: 96, | ||
| 2056 | message: "Transaction counter is exhausted", | ||
| 2057 | })?; | ||
| 2058 | let changed_bytes = count ^ transactions; | ||
| 2059 | let commit_byte = (31 - changed_bytes.leading_zeros()) / 8; | ||
| 2060 | let ceiling = transactions | ((1_u32 << (commit_byte * 8)) - 1); | ||
| 2061 | let mut entries = Vec::new(); | ||
| 2062 | for (id, count) in std::mem::take(&mut self.counts) { | ||
| 2063 | entries.extend_from_slice(&id.to_le_bytes()); | ||
| 2064 | entries.extend_from_slice( | ||
| 2065 | &u32::try_from(count) | ||
| 2066 | .map_err(|_| Error { | ||
| 2067 | offset: 0, | ||
| 2068 | message: "File-node count exceeds the format limit", | ||
| 2069 | })? | ||
| 2070 | .to_le_bytes(), | ||
| 2071 | ); | ||
| 2072 | } | ||
| 2073 | let (mut log_chunk, mut used) = self.state.log; | ||
| 2074 | let mut checksum = self.state.log_crc; | ||
| 2075 | let mut crc_used = used; | ||
| 2076 | let mut crc_capacity = (log_chunk.length as usize - 12) & !7; | ||
| 2077 | let mut advance_crc = |checksum, entry: &[u8]| { | ||
| 2078 | if crc_used == crc_capacity { | ||
| 2079 | crc_used = 0; | ||
| 2080 | crc_capacity = 1008; | ||
| 2081 | } | ||
| 2082 | crc_used += 8; | ||
| 2083 | transaction_crc(checksum, entry, file_type, crc_used == crc_capacity) | ||
| 2084 | }; | ||
| 2085 | for entry in entries.chunks_exact(8) { | ||
| 2086 | checksum = advance_crc(checksum, entry); | ||
| 2087 | } | ||
| 2088 | // Past a counter carry, sentinels up to the ceiling follow the one that commits; the | ||
| 2089 | // next transaction's entries overwrite them, as a reader counting sentinels expects. | ||
| 2090 | let committed = entries.len() + 8; | ||
| 2091 | let mut committed_crc = None; | ||
| 2092 | for _ in transactions..=ceiling { | ||
| 2093 | let sentinel_crc = if is_section { !checksum } else { checksum }; | ||
| 2094 | entries.extend_from_slice(&1_u32.to_le_bytes()); | ||
| 2095 | entries.extend_from_slice(&sentinel_crc.to_le_bytes()); | ||
| 2096 | checksum = advance_crc(checksum, &entries[entries.len() - 8..]); | ||
| 2097 | committed_crc.get_or_insert(checksum); | ||
| 2098 | } | ||
| 2099 | let mut log = self.state.log; | ||
| 2100 | let mut remaining = entries.as_slice(); | ||
| 2101 | while !remaining.is_empty() { | ||
| 2102 | let capacity = usize::try_from(log_chunk.length) | ||
| 2103 | .unwrap() | ||
| 2104 | .checked_sub(12) | ||
| 2105 | .map(|size| size & !7) | ||
| 2106 | .filter(|capacity| *capacity >= used) | ||
| 2107 | .ok_or(Error { | ||
| 2108 | offset: usize::try_from(log_chunk.offset).unwrap(), | ||
| 2109 | message: "Transaction fragment has no room for continuation", | ||
| 2110 | })?; | ||
| 2111 | let size = remaining.len().min(capacity - used); | ||
| 2112 | self.write(log_chunk.offset + used as u64, &remaining[..size]); | ||
| 2113 | let written = entries.len() - remaining.len(); | ||
| 2114 | if (written + 1..=written + size).contains(&committed) { | ||
| 2115 | log = (log_chunk, used + committed - written); | ||
| 2116 | } | ||
| 2117 | used += size; | ||
| 2118 | remaining = &remaining[size..]; | ||
| 2119 | if !remaining.is_empty() { | ||
| 2120 | let mut fragment = vec![0; 1024]; | ||
| 2121 | fragment[1008..1016].copy_from_slice(&u64::MAX.to_le_bytes()); | ||
| 2122 | let next = self.append(&fragment)?; | ||
| 2123 | let mut reference = next.offset.to_le_bytes().to_vec(); | ||
| 2124 | reference.extend_from_slice(&(next.length as u32).to_le_bytes()); | ||
| 2125 | self.write(log_chunk.offset + capacity as u64, &reference); | ||
| 2126 | log_chunk = next; | ||
| 2127 | used = 0; | ||
| 2128 | } | ||
| 2129 | } | ||
| 2130 | let length = self.end(); | ||
| 2131 | header[96..100].copy_from_slice(&transactions.to_le_bytes()); | ||
| 2132 | header[196..204].copy_from_slice(&length.to_le_bytes()); | ||
| 2133 | header[212..228].copy_from_slice(&fresh_guid()?); | ||
| 2134 | header[236..252].copy_from_slice(&fresh_guid()?); | ||
| 2135 | let generation = u64::from_le_bytes(header[228..236].try_into().unwrap()) | ||
| 2136 | .checked_add(1) | ||
| 2137 | .ok_or(Error { | ||
| 2138 | offset: 228, | ||
| 2139 | message: "File generation counter is exhausted", | ||
| 2140 | })?; | ||
| 2141 | header[228..236].copy_from_slice(&generation.to_le_bytes()); | ||
| 2142 | self.state.stamp = crate::Stamp { header, length }; | ||
| 2143 | self.state.log = log; | ||
| 2144 | self.state.log_crc = committed_crc.unwrap(); | ||
| 2145 | Ok(Some(( | ||
| 2146 | crate::Transaction { | ||
| 2147 | base: self.base, | ||
| 2148 | append: self.append, | ||
| 2149 | patches: self.patches, | ||
| 2150 | header, | ||
| 2151 | }, | ||
| 2152 | self.state, | ||
| 2153 | ))) | ||
| 1850 | } | 2154 | } |
| 1851 | if counts.is_empty() { | 2155 | } |
| 1852 | return Ok(source.to_vec()); | 2156 | |
| 2157 | /// The `required` nodes of fragment `sequence` of list `id`, which `bytes` at `at` hold and | ||
| 2158 | /// which ends its list. | ||
| 2159 | fn nodes( | ||
| 2160 | bytes: &[u8], | ||
| 2161 | at: Chunk, | ||
| 2162 | id: u32, | ||
| 2163 | sequence: u32, | ||
| 2164 | required: usize, | ||
| 2165 | ) -> Result<Vec<Node<'_>>> { | ||
| 2166 | let fragment = Fragment::parse(bytes, at.offset as usize)?; | ||
| 2167 | if fragment.id != id || fragment.sequence != sequence || !fragment.next.absent() { | ||
| 2168 | return Err(Error { | ||
| 2169 | offset: at.offset as usize, | ||
| 2170 | message: "An appended fragment has the wrong list, sequence or successor", | ||
| 2171 | }); | ||
| 2172 | } | ||
| 2173 | let mut nodes = Vec::new(); | ||
| 2174 | fragment.nodes(required, &mut nodes, |_| Ok(()))?; | ||
| 2175 | if nodes.len() != required { | ||
| 2176 | return Err(Error { | ||
| 2177 | offset: at.offset as usize, | ||
| 2178 | message: "An appended fragment lacks logged nodes", | ||
| 2179 | }); | ||
| 1853 | } | 2180 | } |
| 2181 | Ok(nodes) | ||
| 2182 | } | ||
| 1854 | 2183 | ||
| 1855 | let transactions = store.header.transaction_count.checked_add(1).ok_or(Error { | 2184 | /// A revision `Appending::revision` wrote, as `check_transaction` expects to read it back. |
| 1856 | offset: 96, | 2185 | pub(crate) struct Written<'r, 'a> { |
| 1857 | message: "Transaction counter is exhausted", | 2186 | pub space: ExGuid, |
| 1858 | })?; | 2187 | pub rid: ExGuid, |
| 1859 | let changed_bytes = store.header.transaction_count ^ transactions; | 2188 | pub previous: Option<ExGuid>, |
| 1860 | let commit_byte = (31 - changed_bytes.leading_zeros()) / 8; | 2189 | pub live: &'r LiveRevision<'a>, |
| 1861 | let ceiling = transactions | ((1_u32 << (commit_byte * 8)) - 1); | 2190 | pub commit: &'r Commit, |
| 1862 | let mut entries = Vec::new(); | 2191 | } |
| 1863 | for (id, count) in counts { | 2192 | |
| 1864 | entries.extend_from_slice(&id.to_le_bytes()); | 2193 | /// Reads back what `transaction` appends to the section `before` describes, whose bytes |
| 1865 | entries.extend_from_slice( | 2194 | /// `segments` hold, and checks it stores `revisions` and `payloads` as `after` expects: |
| 1866 | &u32::try_from(count) | 2195 | /// fragments link and decode to the logged counts under the logged checksum; each declared |
| 1867 | .map_err(|_| Error { | 2196 | /// object parses, names identities its group's table holds and objects reachable after its |
| 1868 | offset: 0, | 2197 | /// revision, carries its incremental reference count and, if read-only, its MD5; roots stay |
| 1869 | message: "File-node count exceeds the format limit", | 2198 | /// unless a space is new. Validates nothing the transaction leaves as it was. |
| 1870 | })? | 2199 | pub(crate) fn check_transaction( |
| 1871 | .to_le_bytes(), | 2200 | before: &StoreState, |
| 1872 | ); | 2201 | after: &StoreState, |
| 2202 | transaction: &crate::Transaction, | ||
| 2203 | segments: &[(u64, &[u8])], | ||
| 2204 | revisions: &[Written<'_, '_>], | ||
| 2205 | payloads: &[([u8; 16], &[u8])], | ||
| 2206 | ) -> Result<()> { | ||
| 2207 | let wrong = |message| Error { offset: 0, message }; | ||
| 2208 | let base = transaction.base.length; | ||
| 2209 | // A chunk's bytes with this transaction's writes: nothing read here is patched earlier. | ||
| 2210 | let read = |chunk: Chunk| -> Result<Vec<u8>> { | ||
| 2211 | let (start, end) = (chunk.offset, chunk.offset + chunk.length); | ||
| 2212 | let mut bytes = if start >= base { | ||
| 2213 | transaction | ||
| 2214 | .append | ||
| 2215 | .get((start - base) as usize..(end - base) as usize) | ||
| 2216 | .ok_or(wrong("An appended chunk lies outside the transaction"))? | ||
| 2217 | .to_vec() | ||
| 2218 | } else { | ||
| 2219 | let (offset, segment) = segments | ||
| 2220 | .iter() | ||
| 2221 | .rfind(|(offset, _)| *offset <= start) | ||
| 2222 | .ok_or(wrong("A chunk lies outside the store"))?; | ||
| 2223 | segment | ||
| 2224 | .get((start - offset) as usize..(end - offset) as usize) | ||
| 2225 | .ok_or(wrong("A chunk lies outside the store"))? | ||
| 2226 | .to_vec() | ||
| 2227 | }; | ||
| 2228 | for (offset, patch) in &transaction.patches { | ||
| 2229 | let (from, to) = (start.max(*offset), end.min(offset + patch.len() as u64)); | ||
| 2230 | if from < to { | ||
| 2231 | bytes[(from - start) as usize..(to - start) as usize] | ||
| 2232 | .copy_from_slice(&patch[(from - offset) as usize..(to - offset) as usize]); | ||
| 2233 | } | ||
| 2234 | } | ||
| 2235 | Ok(bytes) | ||
| 2236 | }; | ||
| 2237 | let header = &transaction.header; | ||
| 2238 | let count = |header: &[u8; 1024]| u32::from_le_bytes(header[96..100].try_into().unwrap()); | ||
| 2239 | if *header != after.stamp.header | ||
| 2240 | || count(header) != count(&before.stamp.header) + 1 | ||
| 2241 | || u64::from_le_bytes(header[196..204].try_into().unwrap()) != after.stamp.length | ||
| 2242 | || after.stamp.length != base + transaction.append.len() as u64 | ||
| 2243 | { | ||
| 2244 | return Err(wrong("The transaction header does not commit its bytes")); | ||
| 1873 | } | 2245 | } |
| 1874 | let mut checksum = if is_section { u32::MAX } else { 0 }; | 2246 | |
| 1875 | for fragment in &store.transaction_fragments { | 2247 | // The log: entries from where it ended, through the sentinel that commits them. |
| 1876 | checksum = transaction_crc( | 2248 | let mut counts = BTreeMap::new(); |
| 1877 | checksum, | 2249 | let (mut chunk, mut used) = before.log; |
| 1878 | fragment.entries, | 2250 | let mut checksum = before.log_crc; |
| 1879 | store.header.file_type, | 2251 | loop { |
| 1880 | fragment.entries.len() + 20 > fragment.chunk.length as usize, | 2252 | let capacity = (chunk.length as usize - 12) & !7; |
| 1881 | ); | 2253 | let bytes = read(chunk)?; |
| 2254 | if used == capacity { | ||
| 2255 | let next = u64::from_le_bytes(bytes[capacity..capacity + 8].try_into().unwrap()); | ||
| 2256 | let length = u32::from_le_bytes(bytes[capacity + 8..capacity + 12].try_into().unwrap()); | ||
| 2257 | (chunk, used) = ( | ||
| 2258 | Chunk { | ||
| 2259 | offset: next, | ||
| 2260 | length: u64::from(length), | ||
| 2261 | }, | ||
| 2262 | 0, | ||
| 2263 | ); | ||
| 2264 | continue; | ||
| 2265 | } | ||
| 2266 | let entry = &bytes[used..used + 8]; | ||
| 2267 | used += 8; | ||
| 2268 | let id = u32::from_le_bytes(entry[..4].try_into().unwrap()); | ||
| 2269 | let value = u32::from_le_bytes(entry[4..].try_into().unwrap()); | ||
| 2270 | if id == 1 { | ||
| 2271 | if value != !checksum { | ||
| 2272 | return Err(wrong("The transaction log checksum does not match")); | ||
| 2273 | } | ||
| 2274 | checksum = transaction_crc(checksum, entry, FileType::Section, used == capacity); | ||
| 2275 | break; | ||
| 2276 | } | ||
| 2277 | checksum = transaction_crc(checksum, entry, FileType::Section, used == capacity); | ||
| 2278 | counts.insert(id, value as usize); | ||
| 1882 | } | 2279 | } |
| 1883 | let last_log = store.transaction_fragments.last().unwrap(); | 2280 | if (chunk, used) != after.log || checksum != after.log_crc { |
| 1884 | let mut crc_used = last_log.entries.len(); | 2281 | return Err(wrong("The transaction log ends elsewhere than its state")); |
| 1885 | let mut crc_capacity = (last_log.chunk.length as usize - 12) & !7; | 2282 | } |
| 1886 | let mut advance_crc = |checksum, entry: &[u8]| { | 2283 | |
| 1887 | if crc_used == crc_capacity { | 2284 | // Nodes a list gained: the fragment `after` ends with, linked from where `before` ended. |
| 1888 | crc_used = 0; | 2285 | let gained = |before: Option<&ListTail>, after: &ListTail| -> Result<Vec<u8>> { |
| 1889 | crc_capacity = 1008; | 2286 | if counts.get(&after.id) != Some(&after.nodes) { |
| 1890 | } | 2287 | return Err(wrong("A list's nodes disagree with the transaction log")); |
| 1891 | crc_used += 8; | 2288 | } |
| 1892 | transaction_crc( | 2289 | if let Some(before) = before { |
| 1893 | checksum, | 2290 | let previous = read(before.last)?; |
| 1894 | entry, | 2291 | if Fragment::parse(&previous, before.last.offset as usize)?.next != after.last |
| 1895 | store.header.file_type, | 2292 | || after.fragments != before.fragments + 1 |
| 1896 | crc_used == crc_capacity, | 2293 | || after.id != before.id |
| 1897 | ) | 2294 | { |
| 2295 | return Err(wrong("An appended fragment is not linked to its list")); | ||
| 2296 | } | ||
| 2297 | } | ||
| 2298 | read(after.last) | ||
| 1898 | }; | 2299 | }; |
| 1899 | for entry in entries.chunks_exact(8) { | 2300 | for written in revisions { |
| 1900 | checksum = advance_crc(checksum, entry); | 2301 | let Written { |
| 2302 | space, | ||
| 2303 | rid, | ||
| 2304 | live, | ||
| 2305 | commit, | ||
| 2306 | .. | ||
| 2307 | } = *written; | ||
| 2308 | let old = before.spaces.get(&space); | ||
| 2309 | let new = after | ||
| 2310 | .spaces | ||
| 2311 | .get(&space) | ||
| 2312 | .ok_or(wrong("A sealed space has no list"))?; | ||
| 2313 | let bytes = gained(old, new)?; | ||
| 2314 | let manifest = nodes( | ||
| 2315 | &bytes, | ||
| 2316 | new.last, | ||
| 2317 | new.id, | ||
| 2318 | old.map_or(0, |old| old.fragments), | ||
| 2319 | new.nodes - old.map_or(0, |old| old.nodes), | ||
| 2320 | )?; | ||
| 2321 | let mut manifest = manifest.iter(); | ||
| 2322 | let mut next = || { | ||
| 2323 | manifest | ||
| 2324 | .next() | ||
| 2325 | .ok_or(wrong("A revision manifest is truncated")) | ||
| 2326 | }; | ||
| 2327 | let mut node = next()?; | ||
| 2328 | if old.is_none() { | ||
| 2329 | let mut c = Cursor { | ||
| 2330 | bytes: node.payload, | ||
| 2331 | offset: node.offset, | ||
| 2332 | }; | ||
| 2333 | if node.id != 0x14 || c.exguid()? != space { | ||
| 2334 | return Err(wrong("A new space's revision list names another space")); | ||
| 2335 | } | ||
| 2336 | node = next()?; | ||
| 2337 | } | ||
| 2338 | let mut c = Cursor { | ||
| 2339 | bytes: node.payload, | ||
| 2340 | offset: node.offset, | ||
| 2341 | }; | ||
| 2342 | let dependency = if commit.checkpoint { | ||
| 2343 | ExGuid::default() | ||
| 2344 | } else { | ||
| 2345 | written | ||
| 2346 | .previous | ||
| 2347 | .ok_or(wrong("A dependent revision follows none"))? | ||
| 2348 | }; | ||
| 2349 | if node.id != 0x1e | ||
| 2350 | || c.exguid()? != rid | ||
| 2351 | || c.exguid()? != dependency | ||
| 2352 | || c.read::<4>()? != 1_u32.to_le_bytes() | ||
| 2353 | || c.read::<2>()? != [0, 0] | ||
| 2354 | { | ||
| 2355 | return Err(wrong( | ||
| 2356 | "A revision starts with the wrong identity or dependency", | ||
| 2357 | )); | ||
| 2358 | } | ||
| 2359 | let mut declared = BTreeSet::new(); | ||
| 2360 | let mut roots = BTreeMap::new(); | ||
| 2361 | loop { | ||
| 2362 | let node = next()?; | ||
| 2363 | match node.id { | ||
| 2364 | 0xb0 => { | ||
| 2365 | let Some(Reference::NodeList(at)) = node.reference else { | ||
| 2366 | return Err(wrong("An object group lacks its list")); | ||
| 2367 | }; | ||
| 2368 | let bytes = read(at)?; | ||
| 2369 | let id = Fragment::parse(&bytes, at.offset as usize)?.id; | ||
| 2370 | let required = *counts | ||
| 2371 | .get(&id) | ||
| 2372 | .ok_or(wrong("An object group is not logged"))?; | ||
| 2373 | let group = nodes(&bytes, at, id, 0, required)?; | ||
| 2374 | let mut table = BTreeMap::new(); | ||
| 2375 | let mut override_crc = u32::MAX; | ||
| 2376 | for item in &group { | ||
| 2377 | let mut c = Cursor { | ||
| 2378 | bytes: item.payload, | ||
| 2379 | offset: item.offset, | ||
| 2380 | }; | ||
| 2381 | match item.id { | ||
| 2382 | 0xb4 | 0x22 | 0x28 | 0xb8 => {} | ||
| 2383 | 0x24 => { | ||
| 2384 | table.insert(u32::from_le_bytes(c.read()?), c.read::<16>()?); | ||
| 2385 | } | ||
| 2386 | 0xa5 | 0xc5 | 0x73 => { | ||
| 2387 | let id = c.compact(&table)?; | ||
| 2388 | let object = live | ||
| 2389 | .revision | ||
| 2390 | .objects | ||
| 2391 | .get(&id) | ||
| 2392 | .ok_or(wrong("A revision declares an unknown object"))?; | ||
| 2393 | let jcid = u32::from_le_bytes(c.read()?); | ||
| 2394 | let flags = if item.id == 0x73 { | ||
| 2395 | None | ||
| 2396 | } else { | ||
| 2397 | Some(c.read::<1>()?[0]) | ||
| 2398 | }; | ||
| 2399 | let count = u32::from_le_bytes(c.read()?); | ||
| 2400 | if !declared.insert(id) | ||
| 2401 | || jcid != object.jcid | ||
| 2402 | || count != object.reference_count | ||
| 2403 | { | ||
| 2404 | return Err(wrong( | ||
| 2405 | "A declaration disagrees with its object or its count", | ||
| 2406 | )); | ||
| 2407 | } | ||
| 2408 | override_crc = | ||
| 2409 | crc(override_crc, &count.to_le_bytes(), FileType::Section); | ||
| 2410 | if item.id == 0x73 { | ||
| 2411 | continue; | ||
| 2412 | } | ||
| 2413 | let Some(Reference::Data(at)) = item.reference else { | ||
| 2414 | return Err(wrong("An object declaration lacks its data")); | ||
| 2415 | }; | ||
| 2416 | let data = read(at)?; | ||
| 2417 | let stored = crate::Object { | ||
| 2418 | jcid, | ||
| 2419 | reference_count: count, | ||
| 2420 | data: ObjectData::Properties(&data), | ||
| 2421 | global_ids: Arc::new(table.clone()), | ||
| 2422 | }; | ||
| 2423 | let references = stored.references()?; | ||
| 2424 | let expected = u8::from(!references.objects.is_empty()) | ||
| 2425 | | (u8::from( | ||
| 2426 | !references.object_spaces.is_empty() | ||
| 2427 | || !references.contexts.is_empty(), | ||
| 2428 | ) << 1); | ||
| 2429 | if object.data != ObjectData::Properties(&data) | ||
| 2430 | || flags != Some(expected) | ||
| 2431 | || (item.id == 0xc5) != (jcid & 0x100000 != 0) | ||
| 2432 | || (item.id == 0xc5 && c.read::<16>()? != md5::compute(&data).0) | ||
| 2433 | { | ||
| 2434 | return Err(wrong( | ||
| 2435 | "An object's stored bytes differ from its revision", | ||
| 2436 | )); | ||
| 2437 | } | ||
| 2438 | if live.is_reachable(id) | ||
| 2439 | && !references | ||
| 2440 | .objects | ||
| 2441 | .iter() | ||
| 2442 | .all(|target| live.is_reachable(*target)) | ||
| 2443 | { | ||
| 2444 | return Err(wrong( | ||
| 2445 | "An object references one its revision cannot reach", | ||
| 2446 | )); | ||
| 2447 | } | ||
| 2448 | } | ||
| 2449 | _ => return Err(wrong("Unexpected node in an object group")), | ||
| 2450 | } | ||
| 2451 | } | ||
| 2452 | let node = next()?; | ||
| 2453 | let mut c = Cursor { | ||
| 2454 | bytes: node.payload, | ||
| 2455 | offset: node.offset, | ||
| 2456 | }; | ||
| 2457 | if node.id != 0x84 | ||
| 2458 | || c.read::<8>()? != [0; 8] | ||
| 2459 | || u32::from_le_bytes(c.read()?) != !override_crc | ||
| 2460 | { | ||
| 2461 | return Err(wrong("An object group's count overrides do not match")); | ||
| 2462 | } | ||
| 2463 | } | ||
| 2464 | 0x5a => { | ||
| 2465 | let mut c = Cursor { | ||
| 2466 | bytes: node.payload, | ||
| 2467 | offset: node.offset, | ||
| 2468 | }; | ||
| 2469 | let id = c.exguid()?; | ||
| 2470 | roots.insert(u32::from_le_bytes(c.read()?), id); | ||
| 2471 | } | ||
| 2472 | 0x1c => break, | ||
| 2473 | _ => return Err(wrong("Unexpected node in a revision manifest")), | ||
| 2474 | } | ||
| 2475 | } | ||
| 2476 | let expected: BTreeSet<ExGuid> = if commit.checkpoint { | ||
| 2477 | live.revision.objects.keys().copied().collect() | ||
| 2478 | } else { | ||
| 2479 | commit.changed.clone() | ||
| 2480 | }; | ||
| 2481 | if declared != expected | ||
| 2482 | || (commit.checkpoint && roots != live.revision.roots) | ||
| 2483 | || (!commit.checkpoint && !roots.is_empty()) | ||
| 2484 | { | ||
| 2485 | return Err(wrong( | ||
| 2486 | "A revision declares other objects or roots than it changed", | ||
| 2487 | )); | ||
| 2488 | } | ||
| 1901 | } | 2489 | } |
| 1902 | for _ in transactions..=ceiling { | 2490 | |
| 1903 | let sentinel_crc = if is_section { !checksum } else { checksum }; | 2491 | if after.root.nodes != before.root.nodes { |
| 1904 | entries.extend_from_slice(&1_u32.to_le_bytes()); | 2492 | let bytes = gained(Some(&before.root), &after.root)?; |
| 1905 | entries.extend_from_slice(&sentinel_crc.to_le_bytes()); | 2493 | let added = nodes( |
| 1906 | checksum = advance_crc(checksum, &entries[entries.len() - 8..]); | 2494 | &bytes, |
| 2495 | after.root.last, | ||
| 2496 | after.root.id, | ||
| 2497 | before.root.fragments, | ||
| 2498 | after.root.nodes - before.root.nodes, | ||
| 2499 | )?; | ||
| 2500 | let created = revisions | ||
| 2501 | .iter() | ||
| 2502 | .filter(|written| !before.spaces.contains_key(&written.space)) | ||
| 2503 | .count(); | ||
| 2504 | let spaces = added.iter().filter(|node| node.id == 8).count(); | ||
| 2505 | if spaces != created || added.iter().any(|node| !matches!(node.id, 8 | 0x90)) { | ||
| 2506 | return Err(wrong( | ||
| 2507 | "The root list gains other nodes than new spaces declare", | ||
| 2508 | )); | ||
| 2509 | } | ||
| 1907 | } | 2510 | } |
| 1908 | let mut log_chunk = last_log.chunk; | 2511 | if !payloads.is_empty() { |
| 1909 | let mut used = last_log.entries.len(); | 2512 | let files = after |
| 1910 | let mut remaining = entries.as_slice(); | 2513 | .files |
| 1911 | while !remaining.is_empty() { | 2514 | .as_ref() |
| 1912 | let capacity = usize::try_from(log_chunk.length) | 2515 | .ok_or(wrong("Embedded payloads have no store"))?; |
| 1913 | .unwrap() | 2516 | let bytes = gained(before.files.as_ref(), files)?; |
| 1914 | .checked_sub(12) | 2517 | let declared = nodes( |
| 1915 | .map(|size| size & !7) | 2518 | &bytes, |
| 1916 | .filter(|capacity| *capacity >= used) | 2519 | files.last, |
| 1917 | .ok_or(Error { | 2520 | files.id, |
| 1918 | offset: usize::try_from(log_chunk.offset).unwrap(), | 2521 | before.files.map_or(0, |files| files.fragments), |
| 1919 | message: "Transaction fragment has no room for continuation", | 2522 | payloads.len(), |
| 1920 | })?; | 2523 | )?; |
| 1921 | let offset = usize::try_from(log_chunk.offset).unwrap(); | 2524 | for ((guid, payload), node) in payloads.iter().zip(&declared) { |
| 1922 | let size = remaining.len().min(capacity - used); | 2525 | let Some(Reference::Data(at)) = node.reference else { |
| 1923 | output[offset + used..offset + used + size].copy_from_slice(&remaining[..size]); | 2526 | return Err(wrong("A payload declaration lacks its data")); |
| 1924 | remaining = &remaining[size..]; | 2527 | }; |
| 1925 | if !remaining.is_empty() { | 2528 | let data = read(at)?; |
| 1926 | let mut fragment = vec![0; 1024]; | 2529 | let blob = crate::files::payload(&data, at.offset as usize); |
| 1927 | fragment[1008..1016].copy_from_slice(&u64::MAX.to_le_bytes()); | 2530 | if node.id != 0x94 || node.payload != guid || blob.ok() != Some(*payload) { |
| 1928 | let next = append(&mut output, &fragment)?; | 2531 | return Err(wrong("An embedded payload differs from its declaration")); |
| 1929 | output[offset + capacity..offset + capacity + 8] | 2532 | } |
| 1930 | .copy_from_slice(&next.offset.to_le_bytes()); | ||
| 1931 | output[offset + capacity + 8..offset + capacity + 12] | ||
| 1932 | .copy_from_slice(&(next.length as u32).to_le_bytes()); | ||
| 1933 | log_chunk = next; | ||
| 1934 | used = 0; | ||
| 1935 | } | 2533 | } |
| 1936 | } | 2534 | } |
| 1937 | output[96..100].copy_from_slice(&transactions.to_le_bytes()); | 2535 | Ok(()) |
| 1938 | let length = u64::try_from(output.len()).unwrap(); | ||
| 1939 | output[196..204].copy_from_slice(&length.to_le_bytes()); | ||
| 1940 | output[212..228].copy_from_slice(&fresh_guid()?); | ||
| 1941 | output[236..252].copy_from_slice(&fresh_guid()?); | ||
| 1942 | let generation = store.header.generation.checked_add(1).ok_or(Error { | ||
| 1943 | offset: 228, | ||
| 1944 | message: "File generation counter is exhausted", | ||
| 1945 | })?; | ||
| 1946 | output[228..236].copy_from_slice(&generation.to_le_bytes()); | ||
| 1947 | Ok(output) | ||
| 1948 | } | 2536 | } |
crates/onestore/src/write/tests/multi.rs+12-4| ... | @@ -186,7 +186,9 @@ fn interrupted_multi_space_publication_never_exposes_a_partial_nesting() { | ... | @@ -186,7 +186,9 @@ fn interrupted_multi_space_publication_never_exposes_a_partial_nesting() { |
| 186 | write_limit, | 186 | write_limit, |
| 187 | random: 1951, | 187 | random: 1951, |
| 188 | }; | 188 | }; |
| 189 | crate::commit::commit_bytes(&mut complete, SOURCE, &written).unwrap(); | 189 | crate::commit::Transaction::between(SOURCE, &written) |
| 190 | .commit(&mut complete) | ||
| 191 | .unwrap(); | ||
| 190 | assert_eq!(complete.durable, written); | 192 | assert_eq!(complete.durable, written); |
| 191 | for fail_at in 1..=complete.operation { | 193 | for fail_at in 1..=complete.operation { |
| 192 | let mut interrupted = disk::Disk { | 194 | let mut interrupted = disk::Disk { |
| ... | @@ -197,7 +199,9 @@ fn interrupted_multi_space_publication_never_exposes_a_partial_nesting() { | ... | @@ -197,7 +199,9 @@ fn interrupted_multi_space_publication_never_exposes_a_partial_nesting() { |
| 197 | write_limit, | 199 | write_limit, |
| 198 | random: 1951 + fail_at as u64, | 200 | random: 1951 + fail_at as u64, |
| 199 | }; | 201 | }; |
| 200 | crate::commit::commit_bytes(&mut interrupted, SOURCE, &written).unwrap_err(); | 202 | crate::commit::Transaction::between(SOURCE, &written) |
| 203 | .commit(&mut interrupted) | ||
| 204 | .unwrap_err(); | ||
| 201 | let observed = current::current(&interrupted.durable); | 205 | let observed = current::current(&interrupted.durable); |
| 202 | assert!( | 206 | assert!( |
| 203 | observed == old || observed == new, | 207 | observed == old || observed == new, |
| ... | @@ -286,7 +290,9 @@ fn repeated_multi_space_edits_cross_counter_carries_and_checkpoint_each_space() | ... | @@ -286,7 +290,9 @@ fn repeated_multi_space_edits_cross_counter_carries_and_checkpoint_each_space() |
| 286 | write_limit: 4096, | 290 | write_limit: 4096, |
| 287 | random: 1952, | 291 | random: 1952, |
| 288 | }; | 292 | }; |
| 289 | crate::commit::commit_bytes(&mut complete, &source, &written).unwrap(); | 293 | crate::commit::Transaction::between(&source, &written) |
| 294 | .commit(&mut complete) | ||
| 295 | .unwrap(); | ||
| 290 | for fail_at in 1..=complete.operation { | 296 | for fail_at in 1..=complete.operation { |
| 291 | let mut interrupted = disk::Disk { | 297 | let mut interrupted = disk::Disk { |
| 292 | visible: source.clone(), | 298 | visible: source.clone(), |
| ... | @@ -296,7 +302,9 @@ fn repeated_multi_space_edits_cross_counter_carries_and_checkpoint_each_space() | ... | @@ -296,7 +302,9 @@ fn repeated_multi_space_edits_cross_counter_carries_and_checkpoint_each_space() |
| 296 | write_limit: 4096, | 302 | write_limit: 4096, |
| 297 | random: 1952 + fail_at as u64, | 303 | random: 1952 + fail_at as u64, |
| 298 | }; | 304 | }; |
| 299 | crate::commit::commit_bytes(&mut interrupted, &source, &written).unwrap_err(); | 305 | crate::commit::Transaction::between(&source, &written) |
| 306 | .commit(&mut interrupted) | ||
| 307 | .unwrap_err(); | ||
| 300 | let observed = current::current(&interrupted.durable); | 308 | let observed = current::current(&interrupted.durable); |
| 301 | assert!(observed == old || observed == new, "{step}:{fail_at}"); | 309 | assert!(observed == old || observed == new, "{step}:{fail_at}"); |
| 302 | } | 310 | } |
crates/onestore/tests/edit.rs+11-2| ... | @@ -210,7 +210,7 @@ fn confirming_visible_text_requires_flush_without_another_revision() { | ... | @@ -210,7 +210,7 @@ fn confirming_visible_text_requires_flush_without_another_revision() { |
| 210 | } | 210 | } |
| 211 | 211 | ||
| 212 | #[test] | 212 | #[test] |
| 213 | fn confirmation_rejects_changed_or_truncated_physical_tail_before_any_write() { | 213 | fn confirmation_rejects_a_changed_header_or_length_before_any_write() { |
| 214 | let (sid, oid) = target(SOURCE); | 214 | let (sid, oid) = target(SOURCE); |
| 215 | let mut source = SOURCE.to_vec(); | 215 | let mut source = SOURCE.to_vec(); |
| 216 | source.resize(3 * 1024 * 1024 + 131, 0); | 216 | source.resize(3 * 1024 * 1024 + 131, 0); |
| ... | @@ -223,7 +223,7 @@ fn confirmation_rejects_changed_or_truncated_physical_tail_before_any_write() { | ... | @@ -223,7 +223,7 @@ fn confirmation_rejects_changed_or_truncated_physical_tail_before_any_write() { |
| 223 | if let trace::Event::Write(offset, bytes) = &disk.events[0] { | 223 | if let trace::Event::Write(offset, bytes) = &disk.events[0] { |
| 224 | panic!("Confirmation wrote {} bytes at {offset}", bytes.len()); | 224 | panic!("Confirmation wrote {} bytes at {offset}", bytes.len()); |
| 225 | } | 225 | } |
| 226 | for changed in [65535, 65536, 1048575, 1048576, 2097152, source.len() - 1] { | 226 | for changed in [96, 128, 212, 1023] { |
| 227 | disk.bytes.clone_from(&source); | 227 | disk.bytes.clone_from(&source); |
| 228 | disk.bytes[changed] ^= 1; | 228 | disk.bytes[changed] ^= 1; |
| 229 | disk.events.clear(); | 229 | disk.events.clear(); |
| ... | @@ -232,9 +232,18 @@ fn confirmation_rejects_changed_or_truncated_physical_tail_before_any_write() { | ... | @@ -232,9 +232,18 @@ fn confirmation_rejects_changed_or_truncated_physical_tail_before_any_write() { |
| 232 | assert_eq!(error.error.kind(), std::io::ErrorKind::ResourceBusy); | 232 | assert_eq!(error.error.kind(), std::io::ErrorKind::ResourceBusy); |
| 233 | assert!(disk.events.is_empty()); | 233 | assert!(disk.events.is_empty()); |
| 234 | } | 234 | } |
| 235 | // Committed content changes only with the header, so the body is never compared. | ||
| 236 | for changed in [1024, 1048576, source.len() - 1] { | ||
| 237 | disk.bytes.clone_from(&source); | ||
| 238 | disk.bytes[changed] ^= 1; | ||
| 239 | disk.events.clear(); | ||
| 240 | onestore::commit_text(&mut disk, &source, sid, oid, 0..0, "").unwrap(); | ||
| 241 | assert!(matches!(disk.events[..], [trace::Event::Flush])); | ||
| 242 | } | ||
| 235 | for length in [source.len() - 1, source.len() + 1] { | 243 | for length in [source.len() - 1, source.len() + 1] { |
| 236 | disk.bytes.clone_from(&source); | 244 | disk.bytes.clone_from(&source); |
| 237 | disk.bytes.resize(length, 0); | 245 | disk.bytes.resize(length, 0); |
| 246 | disk.events.clear(); | ||
| 238 | let error = onestore::commit_text(&mut disk, &source, sid, oid, 0..0, "").unwrap_err(); | 247 | let error = onestore::commit_text(&mut disk, &source, sid, oid, 0..0, "").unwrap_err(); |
| 239 | assert_eq!(error.state, CommitState::NotCommitted); | 248 | assert_eq!(error.state, CommitState::NotCommitted); |
| 240 | assert!(disk.events.is_empty()); | 249 | assert!(disk.events.is_empty()); |
crates/onestore/tests/page.rs+73| ... | @@ -1,6 +1,7 @@ | ... | @@ -1,6 +1,7 @@ |
| 1 | use onestore::{ | 1 | use onestore::{ |
| 2 | ExGuid, Insertion, PageCreation, PreparedEdit, RevisionIndex, Store, | 2 | ExGuid, Insertion, PageCreation, PreparedEdit, RevisionIndex, Store, |
| 3 | document::{Document, Kind}, | 3 | document::{Document, Kind}, |
| 4 | page::{Page, PageObject}, | ||
| 4 | }; | 5 | }; |
| 5 | 6 | ||
| 6 | #[path = "support/current.rs"] | 7 | #[path = "support/current.rs"] |
| ... | @@ -137,6 +138,78 @@ fn created_pages_support_title_edits_and_body_insertion() { | ... | @@ -137,6 +138,78 @@ fn created_pages_support_title_edits_and_body_insertion() { |
| 137 | } | 138 | } |
| 138 | } | 139 | } |
| 139 | 140 | ||
| 141 | /// OneNote 2010 draws level-1 text 27 pt inside an outline whose table stops at entry 0. | ||
| 142 | #[test] | ||
| 143 | fn created_body_outlines_carry_the_indentation_table_onenote_writes() { | ||
| 144 | let source = onestore::create_section("pages.one", "Original", "Author").unwrap(); | ||
| 145 | let store = Store::parse(&source).unwrap(); | ||
| 146 | let index = RevisionIndex::parse(&store).unwrap(); | ||
| 147 | let document = Document::parse(&index).unwrap(); | ||
| 148 | let (space, page) = document.pages().unwrap()[0]; | ||
| 149 | let [first] = document.active(space).unwrap().nodes[&page].children[..] else { | ||
| 150 | panic!() | ||
| 151 | }; | ||
| 152 | let insertion = Insertion::outline(page, 72.0, 144.0, "Second", "Author").unwrap(); | ||
| 153 | let written = PreparedEdit::insert(&source, space, &insertion).unwrap(); | ||
| 154 | let store = Store::parse(written.as_bytes()).unwrap(); | ||
| 155 | let index = RevisionIndex::parse(&store).unwrap(); | ||
| 156 | let document = Document::parse(&index).unwrap(); | ||
| 157 | let view = document.active(space).unwrap(); | ||
| 158 | for outline in [first, insertion.object()] { | ||
| 159 | let Kind::Outline { indents } = &view.nodes[&outline].kind else { | ||
| 160 | panic!() | ||
| 161 | }; | ||
| 162 | assert_eq!(indents, &[18.0, 0.0, 27.0, 27.0]); | ||
| 163 | } | ||
| 164 | } | ||
| 165 | |||
| 166 | #[test] | ||
| 167 | fn a_created_title_outline_is_stored_and_read_as_onenote_stores_it() { | ||
| 168 | let source = onestore::create_section("pages.one", "Original", "Author").unwrap(); | ||
| 169 | let intent = PageCreation::new(None, Some("Title"), "Author").unwrap(); | ||
| 170 | let prepared = PreparedEdit::create_page(&source, &intent).unwrap(); | ||
| 171 | let store = Store::parse(prepared.as_bytes()).unwrap(); | ||
| 172 | let index = RevisionIndex::parse(&store).unwrap(); | ||
| 173 | let document = Document::parse(&index).unwrap(); | ||
| 174 | let view = document.active(intent.space()).unwrap(); | ||
| 175 | let [title] = view.nodes[&intent.object()].structure[..] else { | ||
| 176 | panic!() | ||
| 177 | }; | ||
| 178 | let [outline] = view.nodes[&title].children[..] else { | ||
| 179 | panic!() | ||
| 180 | }; | ||
| 181 | let [paragraph] = view.nodes[&outline].children[..] else { | ||
| 182 | panic!() | ||
| 183 | }; | ||
| 184 | let Kind::Outline { indents } = &view.nodes[&outline].kind else { | ||
| 185 | panic!() | ||
| 186 | }; | ||
| 187 | assert_eq!(indents, &[18.0, 0.0, 27.0, 27.0]); | ||
| 188 | let flags = |id| -> Vec<u32> { | ||
| 189 | view.nodes[&id].extra[0] | ||
| 190 | .iter() | ||
| 191 | .map(|field| field.id) | ||
| 192 | .filter(|id| id >> 24 == 0x88) | ||
| 193 | .collect() | ||
| 194 | }; | ||
| 195 | assert_eq!( | ||
| 196 | flags(outline), | ||
| 197 | [0x88001cf9, 0x88001cb2, 0x88001cb4, 0x88001c91, 0x88001cff] | ||
| 198 | ); | ||
| 199 | assert_eq!(flags(paragraph), [0x88001cb2, 0x88001cb4]); | ||
| 200 | let page = Page::from_space(&document, intent.space()).unwrap(); | ||
| 201 | let [PageObject::Title(title)] = &page.objects[..] else { | ||
| 202 | panic!() | ||
| 203 | }; | ||
| 204 | let [outline] = &title.outlines[..] else { | ||
| 205 | panic!() | ||
| 206 | }; | ||
| 207 | assert!(outline.title); | ||
| 208 | assert_eq!(outline.indents, [18.0, 0.0, 27.0, 27.0]); | ||
| 209 | assert_eq!(outline.min_width, Some(162.0)); | ||
| 210 | assert_eq!(outline.layout.max_height, Some(21.6)); | ||
| 211 | } | ||
| 212 | |||
| 140 | #[test] | 213 | #[test] |
| 141 | fn invalid_page_intents_and_nonleading_anchors_reject_before_publication() { | 214 | fn invalid_page_intents_and_nonleading_anchors_reject_before_publication() { |
| 142 | for title in ["a\nb", "\r", "\0", "\u{fffc}", "\u{fddf}"] { | 215 | for title in ["a\nb", "\r", "\0", "\u{fffc}", "\u{fddf}"] { |
crates/onestore/tests/page_math.rs+39| ... | @@ -328,3 +328,42 @@ fn equations_are_written_and_read_back() { | ... | @@ -328,3 +328,42 @@ fn equations_are_written_and_read_back() { |
| 328 | .unwrap(); | 328 | .unwrap(); |
| 329 | } | 329 | } |
| 330 | } | 330 | } |
| 331 | |||
| 332 | /// Backspacing through inline math leaves plain text in the model, but the stored text object | ||
| 333 | /// still holds the math runs, which the ordinary text edit refuses. | ||
| 334 | #[test] | ||
| 335 | fn a_paragraph_emptied_of_its_inline_math_saves() { | ||
| 336 | let store = Store::parse(NATIVE).unwrap(); | ||
| 337 | let index = RevisionIndex::parse(&store).unwrap(); | ||
| 338 | let document = Document::parse(&index).unwrap(); | ||
| 339 | let (space, before) = document | ||
| 340 | .pages() | ||
| 341 | .unwrap() | ||
| 342 | .into_iter() | ||
| 343 | .map(|(space, _)| (space, Page::from_space(&document, space).unwrap())) | ||
| 344 | .find(|(_, page)| page.title == "Equation controls") | ||
| 345 | .unwrap(); | ||
| 346 | let mut after = before.clone(); | ||
| 347 | let text = after | ||
| 348 | .objects | ||
| 349 | .iter_mut() | ||
| 350 | .filter_map(|object| match object { | ||
| 351 | PageObject::Outline(outline) => Some(outline), | ||
| 352 | _ => None, | ||
| 353 | }) | ||
| 354 | .flat_map(|outline| &mut outline.paragraphs) | ||
| 355 | .filter_map(|paragraph| match &mut paragraph.content { | ||
| 356 | onestore::page::ParagraphContent::Text(text) => Some(text), | ||
| 357 | _ => None, | ||
| 358 | }) | ||
| 359 | .find(|text| text.text.text() == "Inline 𝛼+𝛽") | ||
| 360 | .unwrap(); | ||
| 361 | let plain = text.text.spans()[0].format.clone(); | ||
| 362 | assert_ne!(plain.math, Some(true)); | ||
| 363 | text.text = onestore::page::Paragraph::new(String::new(), plain); | ||
| 364 | let written = onestore::PreparedEdit::page(NATIVE, space, &after, "Author").unwrap(); | ||
| 365 | let store = Store::parse(written.as_bytes()).unwrap(); | ||
| 366 | let index = RevisionIndex::parse(&store).unwrap(); | ||
| 367 | let document = Document::parse(&index).unwrap(); | ||
| 368 | assert_eq!(Page::from_space(&document, space).unwrap(), after); | ||
| 369 | } |
crates/onestore/tests/page_tables.rs+20| ... | @@ -94,6 +94,13 @@ fn referenced(page: &Page) -> std::collections::BTreeSet<ExGuid> { | ... | @@ -94,6 +94,13 @@ fn referenced(page: &Page) -> std::collections::BTreeSet<ExGuid> { |
| 94 | out | 94 | out |
| 95 | } | 95 | } |
| 96 | 96 | ||
| 97 | /// A new cell without its own or a sibling's indentation table takes OneNote's. | ||
| 98 | fn with_native_indents(table: &mut Table) { | ||
| 99 | for cell in table.rows.iter_mut().flat_map(|row| &mut row.cells) { | ||
| 100 | cell.indents = vec![18.0, 0.0, 27.0, 27.0]; | ||
| 101 | } | ||
| 102 | } | ||
| 103 | |||
| 97 | fn assert_same(written: &[u8], space: ExGuid, expected: &Page) -> Page { | 104 | fn assert_same(written: &[u8], space: ExGuid, expected: &Page) -> Page { |
| 98 | let stored = page_in(written, space); | 105 | let stored = page_in(written, space); |
| 99 | let mut expected = expected.clone(); | 106 | let mut expected = expected.clone(); |
| ... | @@ -224,6 +231,9 @@ fn cell_shading_and_indents_change_in_place() { | ... | @@ -224,6 +231,9 @@ fn cell_shading_and_indents_change_in_place() { |
| 224 | assert_eq!(cell.shading, None); | 231 | assert_eq!(cell.shading, None); |
| 225 | cell.shading = Some(0x0000ffff); | 232 | cell.shading = Some(0x0000ffff); |
| 226 | let original_indents = cell.indents.clone(); | 233 | let original_indents = cell.indents.clone(); |
| 234 | cell.indents.clear(); | ||
| 235 | assert!(PreparedEdit::page(TREES, space, &after, AUTHOR).is_err()); | ||
| 236 | let cell = &mut table_mut(&mut after).rows[0].cells[0]; | ||
| 227 | cell.indents = vec![18.0, 0.0, 27.0, 27.0]; | 237 | cell.indents = vec![18.0, 0.0, 27.0, 27.0]; |
| 228 | let written = PreparedEdit::page(TREES, space, &after, AUTHOR).unwrap(); | 238 | let written = PreparedEdit::page(TREES, space, &after, AUTHOR).unwrap(); |
| 229 | let stored = assert_same(written.as_bytes(), space, &after); | 239 | let stored = assert_same(written.as_bytes(), space, &after); |
| ... | @@ -277,6 +287,15 @@ fn a_table_nests_inside_a_cell_and_is_removed_again() { | ... | @@ -277,6 +287,15 @@ fn a_table_nests_inside_a_cell_and_is_removed_again() { |
| 277 | .paragraphs | 287 | .paragraphs |
| 278 | .push(holder); | 288 | .push(holder); |
| 279 | let written = PreparedEdit::page(TREES, space, &after, AUTHOR).unwrap(); | 289 | let written = PreparedEdit::page(TREES, space, &after, AUTHOR).unwrap(); |
| 290 | let ParagraphContent::Table(nested) = &mut table_mut(&mut after).rows[0].cells[1] | ||
| 291 | .paragraphs | ||
| 292 | .last_mut() | ||
| 293 | .unwrap() | ||
| 294 | .content | ||
| 295 | else { | ||
| 296 | panic!() | ||
| 297 | }; | ||
| 298 | with_native_indents(nested); | ||
| 280 | let stored = assert_same(written.as_bytes(), space, &after); | 299 | let stored = assert_same(written.as_bytes(), space, &after); |
| 281 | if let Some(directory) = std::env::var_os("ONESTORE_NESTED_TABLE_EXPORT") { | 300 | if let Some(directory) = std::env::var_os("ONESTORE_NESTED_TABLE_EXPORT") { |
| 282 | let directory = std::path::PathBuf::from(directory); | 301 | let directory = std::path::PathBuf::from(directory); |
| ... | @@ -376,6 +395,7 @@ fn a_new_table_is_created_on_a_fresh_page() { | ... | @@ -376,6 +395,7 @@ fn a_new_table_is_created_on_a_fresh_page() { |
| 376 | body_paragraphs(&mut after).push(holder); | 395 | body_paragraphs(&mut after).push(holder); |
| 377 | body_paragraphs(&mut after).push(cell_paragraph(&template, "After the table")); | 396 | body_paragraphs(&mut after).push(cell_paragraph(&template, "After the table")); |
| 378 | let written = PreparedEdit::page(&source, space, &after, AUTHOR).unwrap(); | 397 | let written = PreparedEdit::page(&source, space, &after, AUTHOR).unwrap(); |
| 398 | with_native_indents(table_mut(&mut after)); | ||
| 379 | assert_same(written.as_bytes(), space, &after); | 399 | assert_same(written.as_bytes(), space, &after); |
| 380 | if let Some(directory) = std::env::var_os("ONESTORE_TABLE_EXPORT") { | 400 | if let Some(directory) = std::env::var_os("ONESTORE_TABLE_EXPORT") { |
| 381 | let directory = std::path::PathBuf::from(directory); | 401 | let directory = std::path::PathBuf::from(directory); |
crates/onestore/tests/page_writer.rs+11-2| ... | @@ -621,8 +621,10 @@ fn unsupported_model_edits_are_rejected_before_writing() { | ... | @@ -621,8 +621,10 @@ fn unsupported_model_edits_are_rejected_before_writing() { |
| 621 | assert!(PreparedEdit::page(TREES, space, &page, "a\0b").is_err()); | 621 | assert!(PreparedEdit::page(TREES, space, &page, "a\0b").is_err()); |
| 622 | } | 622 | } |
| 623 | 623 | ||
| 624 | /// Unset run values stay unset; only the language, which MS-ONE requires on every run, comes | ||
| 625 | /// from the insertion. | ||
| 624 | #[test] | 626 | #[test] |
| 625 | fn a_new_paragraph_with_unspecified_formatting_takes_the_insertion_default() { | 627 | fn a_new_paragraph_with_unspecified_formatting_rereads_with_only_the_required_language() { |
| 626 | let (space, mut page) = page_by_title(OUTLINES, "Move leaf down"); | 628 | let (space, mut page) = page_by_title(OUTLINES, "Move leaf down"); |
| 627 | let text = onestore::page::TextObject { | 629 | let text = onestore::page::TextObject { |
| 628 | id: new_id().unwrap(), | 630 | id: new_id().unwrap(), |
| ... | @@ -662,7 +664,14 @@ fn a_new_paragraph_with_unspecified_formatting_takes_the_insertion_default() { | ... | @@ -662,7 +664,14 @@ fn a_new_paragraph_with_unspecified_formatting_takes_the_insertion_default() { |
| 662 | .unwrap(); | 664 | .unwrap(); |
| 663 | assert_eq!(read_back.text.text(), "Plain 東京"); | 665 | assert_eq!(read_back.text.text(), "Plain 東京"); |
| 664 | let format = read_back.text.format_at(0).unwrap(); | 666 | let format = read_back.text.format_at(0).unwrap(); |
| 665 | assert!(format.font.is_some() && format.font_size.is_some()); | 667 | assert!(format.language.is_some()); |
| 668 | assert_eq!( | ||
| 669 | format, | ||
| 670 | &onestore::document::Format { | ||
| 671 | language: format.language, | ||
| 672 | ..Default::default() | ||
| 673 | } | ||
| 674 | ); | ||
| 666 | assert_eq!( | 675 | assert_eq!( |
| 667 | PreparedEdit::page(written.as_bytes(), space, &stored, AUTHOR) | 676 | PreparedEdit::page(written.as_bytes(), space, &stored, AUTHOR) |
| 668 | .unwrap() | 677 | .unwrap() |
crates/onestore/tests/paragraph.rs+34-14| ... | @@ -117,12 +117,8 @@ fn joins_match_native_graphs_tags_and_inherited_character_styles() { | ... | @@ -117,12 +117,8 @@ fn joins_match_native_graphs_tags_and_inherited_character_styles() { |
| 117 | let intent = ParagraphJoin::new(left, right, "Join author").unwrap(); | 117 | let intent = ParagraphJoin::new(left, right, "Join author").unwrap(); |
| 118 | let restored = serde_json::from_value(serde_json::to_value(&intent).unwrap()).unwrap(); | 118 | let restored = serde_json::from_value(serde_json::to_value(&intent).unwrap()).unwrap(); |
| 119 | assert_eq!(intent, restored); | 119 | assert_eq!(intent, restored); |
| 120 | let edited = PreparedEdit::join(source, *sid, &restored); | 120 | let edited = PreparedEdit::join(source, *sid, &restored) |
| 121 | if name == "Split before hyperlink" { | 121 | .unwrap_or_else(|error| panic!("{name}: {error}")); |
| 122 | assert!(edited.is_err()); | ||
| 123 | continue; | ||
| 124 | } | ||
| 125 | let edited = edited.unwrap_or_else(|error| panic!("{name}: {error}")); | ||
| 126 | let current_store = Store::parse(edited.as_bytes()).unwrap(); | 122 | let current_store = Store::parse(edited.as_bytes()).unwrap(); |
| 127 | assert_eq!( | 123 | assert_eq!( |
| 128 | current_store.header.transaction_count, | 124 | current_store.header.transaction_count, |
| ... | @@ -296,6 +292,9 @@ fn splits_partition_native_paragraphs_at_every_scalar_boundary() { | ... | @@ -296,6 +292,9 @@ fn splits_partition_native_paragraphs_at_every_scalar_boundary() { |
| 296 | let store = Store::parse(SOURCE).unwrap(); | 292 | let store = Store::parse(SOURCE).unwrap(); |
| 297 | let index = RevisionIndex::parse(&store).unwrap(); | 293 | let index = RevisionIndex::parse(&store).unwrap(); |
| 298 | let document = Document::parse(&index).unwrap(); | 294 | let document = Document::parse(&index).unwrap(); |
| 295 | let native_store = Store::parse(JOIN_FIXTURES[0].0).unwrap(); | ||
| 296 | let native_index = RevisionIndex::parse(&native_store).unwrap(); | ||
| 297 | let native_document = Document::parse(&native_index).unwrap(); | ||
| 299 | for case in manifest["cases"].as_array().unwrap() { | 298 | for case in manifest["cases"].as_array().unwrap() { |
| 300 | let text: ExGuid = serde_json::from_value(case["original_text"].clone()).unwrap(); | 299 | let text: ExGuid = serde_json::from_value(case["original_text"].clone()).unwrap(); |
| 301 | let paragraph: ExGuid = serde_json::from_value(case["original_paragraph"].clone()).unwrap(); | 300 | let paragraph: ExGuid = serde_json::from_value(case["original_paragraph"].clone()).unwrap(); |
| ... | @@ -326,8 +325,18 @@ fn splits_partition_native_paragraphs_at_every_scalar_boundary() { | ... | @@ -326,8 +325,18 @@ fn splits_partition_native_paragraphs_at_every_scalar_boundary() { |
| 326 | let restored = serde_json::from_value(serde_json::to_value(&intent).unwrap()).unwrap(); | 325 | let restored = serde_json::from_value(serde_json::to_value(&intent).unwrap()).unwrap(); |
| 327 | assert_eq!(intent, restored); | 326 | assert_eq!(intent, restored); |
| 328 | let edited = PreparedEdit::split(SOURCE, *sid, &restored); | 327 | let edited = PreparedEdit::split(SOURCE, *sid, &restored); |
| 329 | if case["case"] == "Split before hyperlink" { | 328 | // OneNote 2010 splits before or after a hyperlink and ignores Enter inside one. |
| 330 | assert!(edited.is_err()); | 329 | let link = |at: usize| { |
| 330 | expected | ||
| 331 | .get(at) | ||
| 332 | .is_some_and(|(_, style)| style["hyperlink"] == true) | ||
| 333 | }; | ||
| 334 | if position > 0 | ||
| 335 | && link(position - 1) | ||
| 336 | && link(position) | ||
| 337 | && expected[position].0 != '\u{fddf}' | ||
| 338 | { | ||
| 339 | assert!(edited.is_err(), "{} at {offset}", case["case"]); | ||
| 331 | continue; | 340 | continue; |
| 332 | } | 341 | } |
| 333 | let edited = | 342 | let edited = |
| ... | @@ -380,6 +389,23 @@ fn splits_partition_native_paragraphs_at_every_scalar_boundary() { | ... | @@ -380,6 +389,23 @@ fn splits_partition_native_paragraphs_at_every_scalar_boundary() { |
| 380 | "{} suffix at {offset}", | 389 | "{} suffix at {offset}", |
| 381 | case["case"] | 390 | case["case"] |
| 382 | ); | 391 | ); |
| 392 | if case["offset_utf16"] == *offset { | ||
| 393 | let native = &native_document.spaces[sid]; | ||
| 394 | let native = &native.revisions[&native.contexts[&ExGuid::default()]]; | ||
| 395 | let new_text = serde_json::from_value(case["new_text"].clone()).unwrap(); | ||
| 396 | assert_eq!( | ||
| 397 | characters(after, text), | ||
| 398 | characters(native, text), | ||
| 399 | "{}", | ||
| 400 | case["case"] | ||
| 401 | ); | ||
| 402 | assert_eq!( | ||
| 403 | characters(after, intent.text_object()), | ||
| 404 | characters(native, new_text), | ||
| 405 | "{}", | ||
| 406 | case["case"] | ||
| 407 | ); | ||
| 408 | } | ||
| 383 | let mut children = parent_node.children.clone(); | 409 | let mut children = parent_node.children.clone(); |
| 384 | children.insert( | 410 | children.insert( |
| 385 | children.iter().position(|id| *id == paragraph).unwrap() + 1, | 411 | children.iter().position(|id| *id == paragraph).unwrap() + 1, |
| ... | @@ -511,9 +537,6 @@ fn export_native_paragraph_splits() { | ... | @@ -511,9 +537,6 @@ fn export_native_paragraph_splits() { |
| 511 | let mut source = SOURCE.to_vec(); | 537 | let mut source = SOURCE.to_vec(); |
| 512 | let mut written = Vec::new(); | 538 | let mut written = Vec::new(); |
| 513 | for case in manifest["cases"].as_array().unwrap() { | 539 | for case in manifest["cases"].as_array().unwrap() { |
| 514 | if case["case"] == "Split before hyperlink" { | ||
| 515 | continue; | ||
| 516 | } | ||
| 517 | let text: ExGuid = serde_json::from_value(case["original_text"].clone()).unwrap(); | 540 | let text: ExGuid = serde_json::from_value(case["original_text"].clone()).unwrap(); |
| 518 | let store = Store::parse(&source).unwrap(); | 541 | let store = Store::parse(&source).unwrap(); |
| 519 | let index = RevisionIndex::parse(&store).unwrap(); | 542 | let index = RevisionIndex::parse(&store).unwrap(); |
| ... | @@ -621,9 +644,6 @@ fn export_native_paragraph_joins() { | ... | @@ -621,9 +644,6 @@ fn export_native_paragraph_joins() { |
| 621 | let mut source = source.to_vec(); | 644 | let mut source = source.to_vec(); |
| 622 | let mut manifest = Vec::new(); | 645 | let mut manifest = Vec::new(); |
| 623 | for (name, left, right) in cases { | 646 | for (name, left, right) in cases { |
| 624 | if name == "Split before hyperlink" { | ||
| 625 | continue; | ||
| 626 | } | ||
| 627 | let sid = *document | 647 | let sid = *document |
| 628 | .spaces | 648 | .spaces |
| 629 | .iter() | 649 | .iter() |
crates/onestore/tests/transaction.rs created+107| ... | @@ -0,0 +1,107 @@ | ||
| 1 | use onestore::{ | ||
| 2 | CommitIo, CommitState, ExGuid, PreparedEdit, RevisionIndex, Stamp, Store, | ||
| 3 | document::{Document, Kind}, | ||
| 4 | }; | ||
| 5 | use std::io; | ||
| 6 | |||
| 7 | /// Crosses the 65535 → 65536 counter carry on its next transaction. | ||
| 8 | const SOURCE: &[u8] = | ||
| 9 | include_bytes!("../../../corpus/append/round-01/tx-65535/notebook/synthetic.one"); | ||
| 10 | |||
| 11 | fn text(source: &[u8]) -> (ExGuid, ExGuid) { | ||
| 12 | let store = Store::parse(source).unwrap(); | ||
| 13 | let index = RevisionIndex::parse(&store).unwrap(); | ||
| 14 | let document = Document::parse(&index).unwrap(); | ||
| 15 | for (sid, space) in &document.spaces { | ||
| 16 | let revision = &space.revisions[&space.contexts[&ExGuid::default()]]; | ||
| 17 | for (oid, node) in &revision.nodes { | ||
| 18 | if matches!(node.kind, Kind::RichText { .. }) { | ||
| 19 | return (*sid, *oid); | ||
| 20 | } | ||
| 21 | } | ||
| 22 | } | ||
| 23 | panic!("Missing text fixture") | ||
| 24 | } | ||
| 25 | |||
| 26 | /// A file that records how many bytes a commit reads and writes. | ||
| 27 | struct Counted { | ||
| 28 | bytes: Vec<u8>, | ||
| 29 | read: usize, | ||
| 30 | written: usize, | ||
| 31 | } | ||
| 32 | |||
| 33 | impl CommitIo for Counted { | ||
| 34 | fn read_at(&mut self, offset: u64, output: &mut [u8]) -> io::Result<usize> { | ||
| 35 | let offset = offset as usize; | ||
| 36 | let count = output.len().min(self.bytes.len().saturating_sub(offset)); | ||
| 37 | output[..count].copy_from_slice(&self.bytes[offset..offset + count]); | ||
| 38 | self.read += count; | ||
| 39 | Ok(count) | ||
| 40 | } | ||
| 41 | fn write_at(&mut self, offset: u64, bytes: &[u8]) -> io::Result<usize> { | ||
| 42 | let offset = offset as usize; | ||
| 43 | self.bytes | ||
| 44 | .resize(self.bytes.len().max(offset + bytes.len()), 0); | ||
| 45 | self.bytes[offset..offset + bytes.len()].copy_from_slice(bytes); | ||
| 46 | self.written += bytes.len(); | ||
| 47 | Ok(bytes.len()) | ||
| 48 | } | ||
| 49 | fn flush(&mut self) -> io::Result<()> { | ||
| 50 | Ok(()) | ||
| 51 | } | ||
| 52 | } | ||
| 53 | |||
| 54 | #[test] | ||
| 55 | fn a_transaction_reads_the_header_and_writes_only_its_edit() { | ||
| 56 | let (sid, oid) = text(SOURCE); | ||
| 57 | let edit = PreparedEdit::text(SOURCE, sid, oid, 0..0, "é").unwrap(); | ||
| 58 | let transaction = edit.transaction(); | ||
| 59 | let mut image = SOURCE.to_vec(); | ||
| 60 | transaction.apply(&mut image).unwrap(); | ||
| 61 | assert_eq!(image, edit.as_bytes()); | ||
| 62 | let mut file = Counted { | ||
| 63 | bytes: SOURCE.to_vec(), | ||
| 64 | read: 0, | ||
| 65 | written: 0, | ||
| 66 | }; | ||
| 67 | transaction.commit(&mut file).unwrap(); | ||
| 68 | assert_eq!(file.bytes, edit.as_bytes()); | ||
| 69 | assert!(file.read <= 1024 + 2, "read {} bytes", file.read); | ||
| 70 | let appended = edit.as_bytes().len() - SOURCE.len(); | ||
| 71 | assert!( | ||
| 72 | file.written < appended + 2048, | ||
| 73 | "wrote {} bytes for {appended} appended", | ||
| 74 | file.written | ||
| 75 | ); | ||
| 76 | // The file has moved past the base: nothing is written, and applying needs the base. | ||
| 77 | let (read, written) = (file.read, file.written); | ||
| 78 | let error = transaction.commit(&mut file).unwrap_err(); | ||
| 79 | assert_eq!(error.state, CommitState::NotCommitted); | ||
| 80 | assert_eq!(error.error.kind(), io::ErrorKind::ResourceBusy); | ||
| 81 | assert_eq!(file.written, written); | ||
| 82 | assert!(file.read - read <= 1024); | ||
| 83 | assert!(transaction.apply(&mut image).is_err()); | ||
| 84 | } | ||
| 85 | |||
| 86 | #[test] | ||
| 87 | fn stamps_name_the_committed_image() { | ||
| 88 | let (sid, oid) = text(SOURCE); | ||
| 89 | let edit = PreparedEdit::text(SOURCE, sid, oid, 0..0, "x").unwrap(); | ||
| 90 | let before = Stamp::of(SOURCE).unwrap(); | ||
| 91 | let after = Stamp::of(edit.as_bytes()).unwrap(); | ||
| 92 | assert_ne!(before, after); | ||
| 93 | assert_eq!(before.length, SOURCE.len() as u64); | ||
| 94 | // An unpublished tail changes the length, so a commit built without it is refused. | ||
| 95 | let mut tail = SOURCE.to_vec(); | ||
| 96 | tail.extend_from_slice(&[0; 8]); | ||
| 97 | assert_ne!(Stamp::of(&tail).unwrap(), before); | ||
| 98 | let mut file = Counted { | ||
| 99 | bytes: tail, | ||
| 100 | read: 0, | ||
| 101 | written: 0, | ||
| 102 | }; | ||
| 103 | let error = edit.commit(&mut file).unwrap_err(); | ||
| 104 | assert_eq!(error.error.kind(), io::ErrorKind::ResourceBusy); | ||
| 105 | assert_eq!(file.written, 0); | ||
| 106 | assert!(Stamp::of(&SOURCE[..1023]).is_err()); | ||
| 107 | } | ||
tools/canvas/README.md+4-4| ... | @@ -50,7 +50,7 @@ snowbound title bar · toolbar · section frame · page tabs · the page as a | ... | @@ -50,7 +50,7 @@ snowbound title bar · toolbar · section frame · page tabs · the page as a |
| 50 | 50 | ||
| 51 | `--notebook FOLDER` opens a notebook laid out as OneNote lays one out. The title bar shows the application's icon and the notebook's name; below it two rows of toolbar buttons follow the Home ribbon's Basic Text group, the first nine tags and zoom (only zoom acts yet). Readable top-level sections are tabs in the notebook's order, each leaning over the next at 45° with a faint highlight along its top, and the open one on top, casting a soft shadow and framing the page and the page tabs on the right in its colour, which shades slightly down the window; a tab that opens rises to meet the frame as its outline fades in. The frame's top corners and the page's corners are rounded concentric with the window's. The open page's tab is the page's colour and joins it with rounded inside corners; subpages are indented. The interface follows the system's light or dark appearance, and in the dark one the page's paper darkens and text left at OneNote's automatic colour turns light (except on a highlight), resolved when painting so nothing re-lays out; explicit colours, highlights, pictures and ink keep theirs, except that the background art of OneNote 2010's page templates, recognised by the picture's hash, paints from bundled vector recreations drawn for the paper. Sections take their stored colour's hue at a saturation and lightness chosen for each appearance. `--ui-font FONT_FILE_OR_FOLDER` sets the interface's typeface in place of the system's; fonts are not bundled. The section's colour is also the interface accent, easing to the next section's when another opens. The search box, New Page and the page-tab toggle sit above the page tabs. Command-F searches the page titles; Enter opens the first match and Escape clears it. A page that also changed on another computer shows Keep mine / Keep theirs above it. `--section FILE TITLE` opens one section the same way at that page. Section files must be regular files; discovery rejects symbolic links. | 51 | `--notebook FOLDER` opens a notebook laid out as OneNote lays one out. The title bar shows the application's icon and the notebook's name; below it two rows of toolbar buttons follow the Home ribbon's Basic Text group, the first nine tags and zoom (only zoom acts yet). Readable top-level sections are tabs in the notebook's order, each leaning over the next at 45° with a faint highlight along its top, and the open one on top, casting a soft shadow and framing the page and the page tabs on the right in its colour, which shades slightly down the window; a tab that opens rises to meet the frame as its outline fades in. The frame's top corners and the page's corners are rounded concentric with the window's. The open page's tab is the page's colour and joins it with rounded inside corners; subpages are indented. The interface follows the system's light or dark appearance, and in the dark one the page's paper darkens and text left at OneNote's automatic colour turns light (except on a highlight), resolved when painting so nothing re-lays out; explicit colours, highlights, pictures and ink keep theirs, except that the background art of OneNote 2010's page templates, recognised by the picture's hash, paints from bundled vector recreations drawn for the paper. Sections take their stored colour's hue at a saturation and lightness chosen for each appearance. `--ui-font FONT_FILE_OR_FOLDER` sets the interface's typeface in place of the system's; fonts are not bundled. The section's colour is also the interface accent, easing to the next section's when another opens. The search box, New Page and the page-tab toggle sit above the page tabs. Command-F searches the page titles; Enter opens the first match and Escape clears it. A page that also changed on another computer shows Keep mine / Keep theirs above it. `--section FILE TITLE` opens one section the same way at that page. Section files must be regular files; discovery rejects symbolic links. |
| 52 | 52 | ||
| 53 | The `ui` crate builds every frame from the application's state. A cache keyed by stable box ids keeps hover, press, focus, scroll and animation values, and the previous frame's layout routes the frame's input before building, so a frame answers input one layout late and paints with its own. Sizes are solved per axis after building: fixed, label-sized, a fraction of an ancestor, or the sum of children, with overflow shared out by each box's strictness. The page is a custom box: `ui` routes it the pointer, wheel, key and input-method events that land on it, in order, and the host hands them to `PageView` and paints the page in a clipped layer of the same frame. Page scrollbars are `ui` widgets over that box; the canvas reports only its scroll bounds. Text fields edit through `draw::edit`, as the page does, so keys, clicks and drags select and move the same way in both. Work the frame asks for (opening pages or sections, page requests, saving) runs after the frame is painted and requests the frame that shows it. | 53 | The `ui` crate builds every frame from the application's state. A cache keyed by stable box ids keeps hover, press, focus, scroll and animation values, and the previous frame's layout routes the frame's input before building, so a frame answers input one layout late and paints with its own. Sizes are solved per axis after building: fixed, label-sized, a fraction of an ancestor, or the sum of children, with overflow shared out by each box's strictness. The page is a custom box: `ui` routes it the pointer, wheel, key and input-method events that land on it, in order, and the host hands them to `PageView` and paints the page in a clipped layer of the same frame. Page scrollbars are `ui` widgets over that box; the canvas reports only its scroll bounds. Text fields edit through `draw::edit`, as the page does, so keys, clicks and drags select and move the same way in both. Work the frame asks for (page requests, saving) runs after the frame is painted and requests the frame that shows it. Opening a section or page reads it on a thread of its own while the current page stays live; the newest read is laid out and replaces the page once the pictures it shows first are drawn, or after 200 ms. |
| 54 | 54 | ||
| 55 | A covered window receives no redraws, so interaction can be scripted: `SNOWBOUND_REPLAY` names a file of `move X Y`, `press`, `release`, `wheel DX DY`, `key NAME`, `type TEXT`, `modifiers [shift] [command]`, `wait MS`, `snapshot PNG_PATH` and `appearance light|dark` lines in logical pixels, and each step and every 16 ms of a wait draws a frame. Snapshots omit the window's traffic lights, which AppKit draws. Replays edit and save like a user, so point them at a copy of a notebook. | 55 | A covered window receives no redraws, so interaction can be scripted: `SNOWBOUND_REPLAY` names a file of `move X Y`, `press`, `release`, `wheel DX DY`, `key NAME`, `type TEXT`, `modifiers [shift] [command]`, `wait MS`, `snapshot PNG_PATH` and `appearance light|dark` lines in logical pixels, and each step and every 16 ms of a wait draws a frame. Snapshots omit the window's traffic lights, which AppKit draws. Replays edit and save like a user, so point them at a copy of a notebook. |
| 56 | 56 | ||
| ... | @@ -66,7 +66,7 @@ The `draw` crate owns the renderer so the page and application chrome share one | ... | @@ -66,7 +66,7 @@ The `draw` crate owns the renderer so the page and application chrome share one |
| 66 | 66 | ||
| 67 | Glyph coverage uses quarter-pixel phases with the vertical sign converted to Swash's Y-up coordinates. The `draw` GPU readback test checks nine successive quarter-pixel translations by their linear-light ink centroids, then requires identical pixels on repaint, cache eviction and renderer reconstruction on the same device. The native caret experiment exposed the previous inverted vertical phase; correcting raster placement does not change line metrics or wrapping. | 67 | Glyph coverage uses quarter-pixel phases with the vertical sign converted to Swash's Y-up coordinates. The `draw` GPU readback test checks nine successive quarter-pixel translations by their linear-light ink centroids, then requires identical pixels on repaint, cache eviction and renderer reconstruction on the same device. The native caret experiment exposed the previous inverted vertical phase; correcting raster placement does not change line metrics or wrapping. |
| 68 | 68 | ||
| 69 | Image textures have a separate 64 MiB / 256-entry cache. Offscreen images do not upload or consume the active-frame budget; a frame whose visible images exceed that budget returns an error. Under pressure, eviction preserves every image needed by the current frame. PNG/JPEG decoding checks encoded size, dimensions and decoded output size, with a best-effort codec allocation limit. These limits do not bound all CPU buffers or driver allocations. Images use linear filtering of premultiplied, linear-light colors; glyphs retain their own sampling/blending policy. Decoded color values are currently interpreted as sRGB; ICC and orientation metadata are not transformed by this experiment. | 69 | Image textures have a separate 64 MiB / 256-entry cache. Offscreen images do not upload or consume the active-frame budget; a frame whose visible images exceed that budget returns an error. Under pressure, eviction preserves every image needed by the current frame, and a texture goes when every copy of its image has. PNG/JPEG decoding reads the size from the header first, checks encoded size and dimensions against a 256 MiB decoding limit with a best-effort codec allocation limit, and shrinks the picture to the size asked for, filtering premultiplied values in sRGB encoding. These limits do not bound all CPU buffers or driver allocations. Images use linear filtering of premultiplied, linear-light colors; glyphs retain their own sampling/blending policy. Decoded color values are currently interpreted as sRGB; ICC and orientation metadata are not transformed by this experiment. |
| 70 | 70 | ||
| 71 | ## Note tags | 71 | ## Note tags |
| 72 | 72 | ||
| ... | @@ -138,7 +138,7 @@ Input tracing logs Winit keyboard/IME, pointer, modifier and focus events, Acces | ... | @@ -138,7 +138,7 @@ Input tracing logs Winit keyboard/IME, pointer, modifier and focus events, Acces |
| 138 | 138 | ||
| 139 | This integration mode keeps imported objects read-only and opens a separate editable canvas to their left. The initial notes width defaults to 240 pt in this mode, and the reference starts 24 pt to its right. Existing text-file and width arguments also work. Typing, outline creation/movement, paste and undo operate on the temporary notes. The close guard protects those notes. Reference text is rendered but is not exposed through the host’s editable accessibility fields. | 139 | This integration mode keeps imported objects read-only and opens a separate editable canvas to their left. The initial notes width defaults to 240 pt in this mode, and the reference starts 24 pt to its right. Existing text-file and width arguments also work. Typing, outline creation/movement, paste and undo operate on the temporary notes. The close guard protects those notes. Reference text is rendered but is not exposed through the host’s editable accessibility fields. |
| 140 | 140 | ||
| 141 | The host and offscreen renderer both use `canvas::gpu::page::PageScene`. The live scene retains decoded image assets and reads current geometry from `CanvasEditor`; the static reference scene owns its imported content. Layout and image decoding happen on construction; drawing reuses those caches. Scene coordinates stay in source document points, while the comparison example applies its captured margin normalization through the viewport. All three shared-scene outputs are pixel-identical to their preceding title-rendering captures. Decoded image residency is capped at 64 MiB per scene, in addition to the renderer’s GPU cache budget; encoded source payloads and temporary decoder allocations are separate. | 141 | The host and offscreen renderer both use `canvas::gpu::page::PageScene`. The live scene reads current geometry from `CanvasEditor`; the static reference scene owns its imported content. Layout happens on construction and drawing reuses it. Pictures only read their headers then: before each frame, `update_pictures` decodes those in view, and half a view around it, on the scene's picture thread at their shown size in device pixels (in half-power-of-two zoom steps, 150 ms after the zoom settles), and paints each once it lands; one that fails to decode paints as a placeholder. Pictures in view shrink together to fit 32 MiB, those around it decode with what room is left, and those unseen longest are let go past it, so no number or size of pictures fails a page. `settle` waits for the whole page, for offscreen drawing. The stored bytes stay shared with the page model and save unchanged. Scene coordinates stay in source document points, while the comparison example applies its captured margin normalization through the viewport. All three shared-scene outputs are pixel-identical to their preceding title-rendering captures. Decoded pictures are capped at 32 MiB per scene, in addition to the renderer’s GPU cache budget; encoded source payloads and temporary decoder allocations are separate. |
| 142 | 142 | ||
| 143 | The host sleeps its caret timer while macOS reports the window occluded and requests a redraw when visibility returns. Input tracing includes draw attempts, surface occlusion/timeouts and submitted presents; submission is not a measurement of display completion. Startup failures propagate a nonzero exit code after the event loop exits. | 143 | The host sleeps its caret timer while macOS reports the window occluded and requests a redraw when visibility returns. Input tracing includes draw attempts, surface occlusion/timeouts and submitted presents; submission is not a measurement of display completion. Startup failures propagate a nonzero exit code after the event loop exits. |
| 144 | 144 | ||
| ... | @@ -150,7 +150,7 @@ The host sleeps its caret timer while macOS reports the window occluded and requ | ... | @@ -150,7 +150,7 @@ The host sleeps its caret timer while macOS reports the window occluded and requ |
| 150 | 150 | ||
| 151 | `--page` opens supported body outlines and title text as temporary editable objects in their source positions. It accepts a section copy and exact page title, without the separate text-file/width arguments. Title text supports editing, wrapping, IME and undo, and remains present when empty. Date text follows its height; clicking the date or time opens the macOS date control and commits into the same undo history. Clicking a picture selects it with OneNote's dashed border, tint and eight handles. Dragging it moves it on the page grid (Option places it freely); edge handles stretch one axis and corner handles keep the aspect ratio, anchored at the opposite side. Background pictures pass clicks through. Moves and resizes share the undo history. Character editing, selection, IME and undo use the same outline model as new pages. Existing structured or tagged paragraphs accept in-paragraph edits; their structural split/join remains restricted. Newly created flat outlines support the full structural workflow. The app never writes the section file. | 151 | `--page` opens supported body outlines and title text as temporary editable objects in their source positions. It accepts a section copy and exact page title, without the separate text-file/width arguments. Title text supports editing, wrapping, IME and undo, and remains present when empty. Date text follows its height; clicking the date or time opens the macOS date control and commits into the same undo history. Clicking a picture selects it with OneNote's dashed border, tint and eight handles. Dragging it moves it on the page grid (Option places it freely); edge handles stretch one axis and corner handles keep the aspect ratio, anchored at the opposite side. Background pictures pass clicks through. Moves and resizes share the undo history. Character editing, selection, IME and undo use the same outline model as new pages. Existing structured or tagged paragraphs accept in-paragraph edits; their structural split/join remains restricted. Newly created flat outlines support the full structural workflow. The app never writes the section file. |
| 152 | 152 | ||
| 153 | `PageScene::from_page` constructs the editor-owned page and decodes its image assets. Imported paint order stays with the editor; each text slot invokes the host’s outline painter. Movement reads the current model geometry without decoding images again. New outlines draw after the imported page. The close guard compares all object layouts, editable text and the creation timestamp with the opening state, including displacement that remains after a title is shortened. | 153 | `PageScene::from_page` constructs the editor-owned page and reads its pictures' headers. Imported paint order stays with the editor; each text slot invokes the host’s outline painter. Movement reads the current model geometry and keeps each picture's raster. New outlines draw after the imported page. The close guard compares all object layouts, editable text and the creation timestamp with the opening state, including displacement that remains after a title is shortened. |
| 154 | 154 | ||
| 155 | The app's private integration test exercises the actual outline painter and accessibility consumer for each imported outline, then verifies text restoration through undo: | 155 | The app's private integration test exercises the actual outline painter and accessibility consumer for each imported outline, then verifies text restoration through undo: |
| 156 | 156 |
tools/test_page_creation.py+3-1| ... | @@ -16,9 +16,11 @@ class PageCreationTest(unittest.TestCase): | ... | @@ -16,9 +16,11 @@ class PageCreationTest(unittest.TestCase): |
| 16 | compare(fixture / 'candidate', fixture / 'cold') | 16 | compare(fixture / 'candidate', fixture / 'cold') |
| 17 | 17 | ||
| 18 | def test_created_pages_and_both_writers_survive_native_cold_reopens(self): | 18 | def test_created_pages_and_both_writers_survive_native_cold_reopens(self): |
| 19 | for source, capture in [('candidate', 'cold'), ('native/notebook', 'native/cold'), ('followup', 'followup/cold')]: | 19 | for source, capture in [('candidate', 'cold'), ('native/notebook', 'native/cold')]: |
| 20 | with self.subTest(source=source): | 20 | with self.subTest(source=source): |
| 21 | compare(FIXTURE / source, FIXTURE / capture) | 21 | compare(FIXTURE / source, FIXTURE / capture) |
| 22 | # OneNote refreshes the renamed titles' stale section metadata copies. | ||
| 23 | self.assertEqual(compare(FIXTURE / 'followup', FIXTURE / 'followup/cold', refresh_metadata=True), 2) | ||
| 22 | 24 | ||
| 23 | def test_duplicate_titles_keep_distinct_pages_through_native_and_rust_edits(self): | 25 | def test_duplicate_titles_keep_distinct_pages_through_native_and_rust_edits(self): |
| 24 | with TemporaryDirectory() as temporary: | 26 | with TemporaryDirectory() as temporary: |
tools/test_page_movement.py+1-1| ... | @@ -111,7 +111,7 @@ class PageMovementTest(unittest.TestCase): | ... | @@ -111,7 +111,7 @@ class PageMovementTest(unittest.TestCase): |
| 111 | with self.assertRaises(AssertionError): | 111 | with self.assertRaises(AssertionError): |
| 112 | compare(fixture / 'notebook', fixture / 'cold') | 112 | compare(fixture / 'notebook', fixture / 'cold') |
| 113 | refreshed = compare(fixture / 'notebook', fixture / 'cold', | 113 | refreshed = compare(fixture / 'notebook', fixture / 'cold', |
| 114 | refresh_metadata_levels=phase == '02-promoted-parent') | 114 | refresh_metadata=phase == '02-promoted-parent') |
| 115 | self.assertEqual(refreshed, int(phase == '02-promoted-parent')) | 115 | self.assertEqual(refreshed, int(phase == '02-promoted-parent')) |
| 116 | output = temporary / phase | 116 | output = temporary / phase |
| 117 | subprocess.run([EXPORTER, fixture / 'notebook/Lifecycle.one', output], check=True) | 117 | subprocess.run([EXPORTER, fixture / 'notebook/Lifecycle.one', output], check=True) |
tools/verify_page_creation.py+7-8| ... | @@ -14,7 +14,7 @@ from document_model import EXPORTER, ordered_pages, view | ... | @@ -14,7 +14,7 @@ from document_model import EXPORTER, ordered_pages, view |
| 14 | compare_document = runpy.run_path(str(Path(__file__).with_name('verify-document.py')))['compare'] | 14 | compare_document = runpy.run_path(str(Path(__file__).with_name('verify-document.py')))['compare'] |
| 15 | 15 | ||
| 16 | 16 | ||
| 17 | def compare(source, capture, *, refresh_metadata_levels=False): | 17 | def compare(source, capture, *, refresh_metadata=False): |
| 18 | files = list(source.glob('*.one')) | 18 | files = list(source.glob('*.one')) |
| 19 | assert len(files) == 1 | 19 | assert len(files) == 1 |
| 20 | with TemporaryDirectory() as temporary: | 20 | with TemporaryDirectory() as temporary: |
| ... | @@ -53,13 +53,12 @@ def compare(source, capture, *, refresh_metadata_levels=False): | ... | @@ -53,13 +53,12 @@ def compare(source, capture, *, refresh_metadata_levels=False): |
| 53 | series = expected[series_id] | 53 | series = expected[series_id] |
| 54 | existing = [p for p in series['extra'][0] if p['id'] == 0x24003442] | 54 | existing = [p for p in series['extra'][0] if p['id'] == 0x24003442] |
| 55 | added = [p for p in observed['nodes'][series_id]['extra'][0] if p['id'] == 0x24003442] | 55 | added = [p for p in observed['nodes'][series_id]['extra'][0] if p['id'] == 0x24003442] |
| 56 | if existing and refresh_metadata_levels: | 56 | if existing and refresh_metadata: |
| 57 | for copy in existing[0]['value']['Objects']: | 57 | for copy in existing[0]['value']['Objects']: |
| 58 | cached = expected[copy]['kind'] | 58 | assert expected[copy]['kind']['type'] == 'Metadata' |
| 59 | assert cached['type'] == 'Metadata' | 59 | current = metadata[copy][1]['kind'] |
| 60 | level = metadata[copy][1]['kind']['level'] | 60 | refreshed += expected[copy]['kind'] != current |
| 61 | refreshed += cached['level'] != level | 61 | expected[copy]['kind'] = current |
| 62 | cached['level'] = level | ||
| 63 | if existing or not added: | 62 | if existing or not added: |
| 64 | continue | 63 | continue |
| 65 | copies = [] | 64 | copies = [] |
| ... | @@ -75,7 +74,7 @@ def compare(source, capture, *, refresh_metadata_levels=False): | ... | @@ -75,7 +74,7 @@ def compare(source, capture, *, refresh_metadata_levels=False): |
| 75 | series['extra'][0].append(added[0]) | 74 | series['extra'][0].append(added[0]) |
| 76 | assert original['roots'] == observed['roots'] | 75 | assert original['roots'] == observed['roots'] |
| 77 | assert expected == observed['nodes'], sid | 76 | assert expected == observed['nodes'], sid |
| 78 | print(f'Passed: {len(pages)} ordered pages and preserved active graphs; {filled} missing section metadata copies filled natively; {refreshed} section metadata levels refreshed') | 77 | print(f'Passed: {len(pages)} ordered pages and preserved active graphs; {filled} missing section metadata copies filled natively; {refreshed} section metadata copies refreshed') |
| 79 | return refreshed | 78 | return refreshed |
| 80 | 79 | ||
| 81 | 80 |