| author | |
| committer | |
| log | bdb38ac1f1a4e8f65b558380079eeab79033b2f8 |
| tree | ef5b5e7b1c3bb3f77e990989a97b878260422867 |
| parent | 6478bd1e6e851e3c9bfe58640dfb03ffb64f09f8 |
| signature | Signed by SSH key SHA256:52mNGHRsVFBDED9IAX5pe+LRWUefqTbxEReunq21QvU |
A second equation-editor session (corpus/math-edit/native-editor-2, typed
by tools/native_math.py with ONESTORE_MATH_EQUATIONS) adds square and
n-th roots, a plain fraction, a limit with an upright function name, a
product, square brackets, an overbar and a hat to the MathML oracle, and
the mapping now reproduces all nine exports byte for byte: radicals render
as msqrt or mroot with the degree second, brackets other than parentheses
name their fences, an accent object is mover accent="true" with the
combining mark exported as its spacing character, an overbar is mover
accent="false" with a stretchy bar, and runs of upright letters are one
function-name identifier that a limit base wraps in a row.
Mouse ink drawn over an outline in OneNote 2010 becomes a page-level
drawing (corpus/ink-edit/native-mouse-over-text, tools/native_handwriting.py),
so handwriting inside text runs cannot be authored in the lab and stays
read-only retained content.
Assisted-by: claude-fable-5.147 files changed, 834 insertions(+), 4 deletions(-)
corpus/ink-edit/README.md+6| ... | @@ -24,3 +24,9 @@ outline grown to contain the strokes' absolute page coordinates. | ... | @@ -24,3 +24,9 @@ outline grown to contain the strokes' absolute page coordinates. |
| 24 | directories while running the tests, then cold-open each with | 24 | directories while running the tests, then cold-open each with |
| 25 | `tools/native_runner.py OUTPUT COLD --expected-pages 1 --collect-notebook | 25 | `tools/native_runner.py OUTPUT COLD --expected-pages 1 --collect-notebook |
| 26 | --screenshots`. | 26 | --screenshots`. |
| 27 | |||
| 28 | `native-mouse-over-text/` is OneNote 2010 receiving mouse strokes over an | ||
| 29 | outline's text and in empty space (`tools/native_handwriting.py`): both | ||
| 30 | became page-level `InkDrawing` objects. Mouse ink never produces | ||
| 31 | handwriting paragraphs or embedded ink in text runs, so those forms stay | ||
| 32 | read-only retained content until a pen-authored fixture exists. |
corpus/ink-edit/native-mouse-over-text/before-read/environment.json created+7| ... | @@ -0,0 +1,7 @@ | ||
| 1 | { | ||
| 2 | "powershell": "5.1.14409.1005", | ||
| 3 | "schema": "xs2010", | ||
| 4 | "hostname": "ONE-M6-9C262230", | ||
| 5 | "cold": true, | ||
| 6 | "onenote": "14.0.4763.1000" | ||
| 7 | } | ||
corpus/ink-edit/native-mouse-over-text/before-read/hierarchy.xml created+2| ... | @@ -0,0 +1,2 @@ | ||
| 1 | <?xml version="1.0"?> | ||
| 2 | <one:Notebook xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" name="notebook" nickname="notebook" ID="{81D3E2FA-1372-4C5B-9A6E-5DBF99B76CED}{1}{B0}" path="C:\one-tests\runs\capture\notebook" lastModifiedTime="2026-09-13T03:08:22.000Z" color="#B49EDE"><one:Section name="links" ID="{13A54437-B3B6-01B2-29B8-E2E4F68FD99E}{1}{B0}" path="C:\one-tests\runs\capture\notebook\links.one" lastModifiedTime="2026-09-13T03:08:22.000Z" color="#8AA8E4"><one:Page ID="{B2A882BE-DBA1-0E8F-3F13-C2840E6C5065}{1}{B0}" name="Read about Rust HYPERLINK &quot;https://example.invalid/rust&quot;the Rust site" dateTime="2026-09-12T09:49:16.000Z" lastModifiedTime="2026-09-13T03:08:22.000Z" pageLevel="1"/></one:Section></one:Notebook> | ||
corpus/ink-edit/native-mouse-over-text/before-read/page-000.xml created+3| ... | @@ -0,0 +1,3 @@ | ||
| 1 | <?xml version="1.0"?> | ||
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{B2A882BE-DBA1-0E8F-3F13-C2840E6C5065}{1}{B0}" name="Read about Rust HYPERLINK &quot;https://example.invalid/rust&quot;the Rust site" dateTime="2026-09-12T09:49:16.000Z" lastModifiedTime="2026-09-13T03:08:22.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-12T09:49:16.000Z" objectID="{13A54437-B3B6-01B2-29B8-E2E4F68FD99E}{23}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="198.3904571533203" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-12T09:49:16.000Z" lastModifiedTime="2026-09-12T09:49:16.000Z" objectID="{13A54437-B3B6-01B2-29B8-E2E4F68FD99E}{24}{B0}" alignment="left"><one:T><![CDATA[Read about Rust <a | ||
| 3 | href="https://example.invalid/rust">the Rust site</a>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> | ||
corpus/ink-edit/native-mouse-over-text/before-read/payloads.json created+3| ... | @@ -0,0 +1,3 @@ | ||
| 1 | [ | ||
| 2 | |||
| 3 | ] | ||
| \ No newline at end of file | |||
corpus/ink-edit/native-mouse-over-text/commands.jsonl created+6| ... | @@ -0,0 +1,6 @@ | ||
| 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-9C262230 -ExpectedPages 1 -KeepOpen", "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} | ||
| 4 | {"command": "powershell -NoProfile -Command \"Rename-Item C:\\one-tests\\runs\\capture\\read before-read\"", "exit": 0, "stdout": "", "stderr": "", "error": null} | ||
| 5 | {"command": "powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File C:\\one-tests\\read-current.ps1 -Root C:\\one-tests\\runs\\capture -CloneHost ONE-M6-9C262230 -UseCurrentCache -ExpectedPages -1", "exit": 0, "stdout": "Read 1 sections and 1 pages.\r\n", "stderr": "", "error": null} | ||
| 6 | {"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/ink-edit/native-mouse-over-text/handwriting.ahk created+34| ... | @@ -0,0 +1,34 @@ | ||
| 1 | ComObject("OneNote.Application").NavigateTo("{B2A882BE-DBA1-0E8F-3F13-C2840E6C5065}{1}{B0}", "", false) | ||
| 2 | WinWait("ahk_exe ONENOTE.EXE", , 30) | ||
| 3 | WinActivate("ahk_exe ONENOTE.EXE") | ||
| 4 | WinWaitActive("ahk_exe ONENOTE.EXE", , 30) | ||
| 5 | WinMaximize("ahk_exe ONENOTE.EXE") | ||
| 6 | Sleep(3000) | ||
| 7 | SendMode("Event") | ||
| 8 | CoordMode("Mouse", "Screen") | ||
| 9 | Click(258,36) | ||
| 10 | Sleep(600) | ||
| 11 | Click(205,69) | ||
| 12 | Sleep(600) | ||
| 13 | ; handwriting inside the outline, below its text line | ||
| 14 | MouseMove(140,160) | ||
| 15 | Click("Down") | ||
| 16 | MouseMove(160,175,25) | ||
| 17 | MouseMove(180,160,25) | ||
| 18 | MouseMove(200,175,25) | ||
| 19 | MouseMove(220,160,25) | ||
| 20 | Click("Up") | ||
| 21 | Sleep(300) | ||
| 22 | MouseClickDrag("Left",240,158,300,178,25) | ||
| 23 | Sleep(300) | ||
| 24 | ; a drawing in empty page space | ||
| 25 | MouseMove(450,320) | ||
| 26 | Click("Down") | ||
| 27 | MouseMove(500,360,25) | ||
| 28 | MouseMove(450,400,25) | ||
| 29 | MouseMove(400,360,25) | ||
| 30 | MouseMove(450,320,25) | ||
| 31 | Click("Up") | ||
| 32 | Sleep(1000) | ||
| 33 | Click(21,74) | ||
| 34 | Sleep(2000) | ||
corpus/ink-edit/native-mouse-over-text/handwriting.json created+13| ... | @@ -0,0 +1,13 @@ | ||
| 1 | { | ||
| 2 | "exit": 0, | ||
| 3 | "stdout": "", | ||
| 4 | "stderr": "", | ||
| 5 | "w": 800, | ||
| 6 | "h": 600, | ||
| 7 | "error": null, | ||
| 8 | "win": { | ||
| 9 | "title": "Read about Rust the Rust site - Microsoft OneNote", | ||
| 10 | "class": "Framework::CFrame", | ||
| 11 | "dialog": false | ||
| 12 | } | ||
| 13 | } | ||
| \ No newline at end of file | |||
corpus/ink-edit/native-mouse-over-text/handwriting.png created| Binary files /dev/null and b/corpus/ink-edit/native-mouse-over-text/handwriting.png differ | |||
corpus/ink-edit/native-mouse-over-text/machine.json created+1| ... | @@ -0,0 +1 @@ | ||
| 1 | {"name": "m6-9c262230", "hostname": "ONE-M6-9C262230"} | ||
corpus/ink-edit/native-mouse-over-text/notebook/Open Notebook.onetoc2 created| Binary files /dev/null and b/corpus/ink-edit/native-mouse-over-text/notebook/Open Notebook.onetoc2 differ | |||
corpus/ink-edit/native-mouse-over-text/notebook/links.one created| Binary files /dev/null and b/corpus/ink-edit/native-mouse-over-text/notebook/links.one differ | |||
corpus/ink-edit/native-mouse-over-text/read/environment.json created+7| ... | @@ -0,0 +1,7 @@ | ||
| 1 | { | ||
| 2 | "powershell": "5.1.14409.1005", | ||
| 3 | "schema": "xs2010", | ||
| 4 | "hostname": "ONE-M6-9C262230", | ||
| 5 | "cold": false, | ||
| 6 | "onenote": "14.0.4763.1000" | ||
| 7 | } | ||
corpus/ink-edit/native-mouse-over-text/read/hierarchy.xml created+2| ... | @@ -0,0 +1,2 @@ | ||
| 1 | <?xml version="1.0"?> | ||
| 2 | <one:Notebook xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" name="notebook" nickname="notebook" ID="{81D3E2FA-1372-4C5B-9A6E-5DBF99B76CED}{1}{B0}" path="C:\one-tests\runs\capture\notebook" lastModifiedTime="2026-09-13T03:10:25.000Z" color="#B49EDE" isCurrentlyViewed="true"><one:Section name="links" ID="{13A54437-B3B6-01B2-29B8-E2E4F68FD99E}{1}{B0}" path="C:\one-tests\runs\capture\notebook\links.one" lastModifiedTime="2026-09-13T03:10:25.000Z" color="#8AA8E4" isCurrentlyViewed="true"><one:Page ID="{B2A882BE-DBA1-0E8F-3F13-C2840E6C5065}{1}{B0}" name="Read about Rust the Rust site" dateTime="2026-09-12T09:49:16.000Z" lastModifiedTime="2026-09-13T03:10:18.000Z" pageLevel="1" isCurrentlyViewed="true"/></one:Section></one:Notebook> | ||
corpus/ink-edit/native-mouse-over-text/read/page-000.xml created+22| ... | @@ -0,0 +1,22 @@ | ||
| 1 | <?xml version="1.0"?> | ||
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{B2A882BE-DBA1-0E8F-3F13-C2840E6C5065}{1}{B0}" name="Read about Rust the Rust site" dateTime="2026-09-12T09:49:16.000Z" lastModifiedTime="2026-09-13T03:10:18.000Z" pageLevel="1" isCurrentlyViewed="true" 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-13T03:10:03.000Z" objectID="{13A54437-B3B6-01B2-29B8-E2E4F68FD99E}{23}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="198.3904571533203" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-12T09:49:16.000Z" lastModifiedTime="2026-09-13T03:10:03.000Z" objectID="{13A54437-B3B6-01B2-29B8-E2E4F68FD99E}{24}{B0}" alignment="left"><one:T><![CDATA[Read about Rust <a | ||
| 3 | href="https://example.invalid/rust">the Rust site</a>]]></one:T></one:OE></one:OEChildren></one:Outline><one:InkDrawing lastModifiedTime="2026-09-13T03:10:14.000Z" objectID="{880F602E-E250-42FE-BA4D-D86F4AAE4E9E}{16}{B0}"><one:Position x="144.0" y="56.2393684387207" z="1"/><one:Size width="45.01417922973633" height="15.05196857452393"/><one:Data>ANoCHQJ6KgFgKgAaH4tCcEWEdYC+0EXZBPqHlTLy7BdBuMAWv/Mr9HreuxOL5V6zSJyboSVat1ES | ||
| 4 | v+xxzMwisUGJDrsTLO56oVjPVIrml8VPjwb4utLhmyLEZm2nDbJYSZodTkA0BmwQAwtIFETtuZAB | ||
| 5 | RSNGIwUIC2VPZmdoZGkZFDIIAMAMAgAAyEEzCACwCQIAAMhBZAMVRgBACr4BSIfwsXeFcUmUzTWa | ||
| 6 | EzmU1mk1gEBmkzgMAgKAQGATWZQCAzOaQGZzSAzOaTWZTOZTNNJrMpmmk1mUzTSazKZppNUymc0m | ||
| 7 | qZTOZTOH7f7t/5pAZnNIDM5pAZnNJrMpnMpmmk1mUzTSaplMyaTVMpmTSaplMyaTVMpmmk1AAAAF | ||
| 8 | FEYAQAAACQQFFEYAAAAAEgARILeLLBPpguVJo6ME9ct7COwKP0AjYQMhZDLQO6ADFUYAQAo/QCKS | ||
| 9 | wgP7+zf4wA==</one:Data></one:InkDrawing><one:InkDrawing lastModifiedTime="2026-09-13T03:10:18.000Z" objectID="{880F602E-E250-42FE-BA4D-D86F4AAE4E9E}{21}{B0}"><one:Position x="263.9905395507812" y="177.7606201171875" z="2"/><one:Size width="75.0330810546875" height="60.00945281982422"/><one:Data>AOMFHQTKAaIBAWAqABofi0JwRYR1gL7QRdkE+oeVMvLsF0G4wBa/8yv0et67E4vlXrNInJuhJVq3 | ||
| 10 | URK/7HHMzCKxQYkOuxMs7nqhWM9UiuaXxU+PBvi60uGbIsRmbacNslhJmh1OQDQGbBADC0gURO25 | ||
| 11 | kAFFI0YjBQgLZU9mZ2hkaRkUMggAwAwCAADIQTMIALAJAgAAyEFkAxVGAEAKxQSNAofyDLeQS2Zz | ||
| 12 | KAwCZzKazSZzCYwGAQGAQGAQGAQGAQFM5pAZnNIDM5pNZlM5pNZlM5pNUymcymaaTWZTNNJqmUzm | ||
| 13 | k1TKZzSaplM7SIBMJnNJrMpnNEBgEBgEBgEBgEBgEBmkzTKawCZTWaTOZTWaTOZTWaJnMprNEzmU | ||
| 14 | zmSazRM5lNZomcyms0FpIBAZlM5pNZlMZhAIDAIDAIDAIDAIDAEymsAmU1gEyms0mcyms0mcyms0 | ||
| 15 | TOZTOZJrNJnMk1miZzKazRM5lNZoLRM5kgMxmU1mkzmU1QCAwCAwCAwCAwCAwCazJM5pAZnNJrMp | ||
| 16 | nNJrMpnNJqmUzmk1TKZzKZppNUymc0mqZTOaTVMpmIfw++eH0maTWaTOAwCZTWAQGAQFAIDM5pAY | ||
| 17 | BNZlAJrMoBNZlM5lM5pNUymc0mqZTMmk1TKZppNSZTNNJqTKZplM00gM0mcymsAgM0msAgMAgMAg | ||
| 18 | MAmsygEBmc0gMzmUAmsymc0msymaaTWZTNNJqTKZppNUymZMpmmk1JlM00mqZYdTSazSAwCazKAQ | ||
| 19 | GAIDAIDNJnAYBMprAJlNYBMprNJnMpnMk1mkzmSazQmcyTWaJnMiazRM5kTWaJnMi0ppNZlAIDM5 | ||
| 20 | ogMAgMAgMAmU1gEBmkzTKawCZTWaTOZTWaJnMprNEzmRNZomcyTWaEzmSZzIms0TOZAFFEYAgAAA | ||
| 21 | CQQFFEYAAAAAEgARIHXkv4XbUV9Btx3j0SO9N8YKABEgsI0hZS1D3QEDFUYAQAoAESDcOupAw5Sd | ||
| 22 | QA==</one:Data></one:InkDrawing></one:Page> | ||
corpus/ink-edit/native-mouse-over-text/read/payloads.json created+3| ... | @@ -0,0 +1,3 @@ | ||
| 1 | [ | ||
| 2 | |||
| 3 | ] | ||
| \ No newline at end of file | |||
corpus/ink-edit/native-mouse-over-text/run.json created+18| ... | @@ -0,0 +1,18 @@ | ||
| 1 | { | ||
| 2 | "notebook": "/Users/clo/dev/one/corpus/link-edit/candidate", | ||
| 3 | "expected_pages": 1, | ||
| 4 | "author": null, | ||
| 5 | "author_timeout_seconds": 600, | ||
| 6 | "inspect": false, | ||
| 7 | "collect_notebook": true, | ||
| 8 | "base": { | ||
| 9 | "file": "win7-office-base.qcow2", | ||
| 10 | "format": "qcow2", | ||
| 11 | "sha256": "a1a4f8fab782ee14885ff801ca2f6347c208fdcfc3513637096f314315c89346", | ||
| 12 | "virtual_size": 68719476736 | ||
| 13 | }, | ||
| 14 | "scripts": { | ||
| 15 | "cold.ps1": "c177fc72ae6c2634d186f5671a880de20b09f9716532c37c69cb13aa15c7e331", | ||
| 16 | "read.ps1": "04013bfcccee40a17e2a225f9b9e96f40a3a8daad9e350609654f8eda3ccbb41" | ||
| 17 | } | ||
| 18 | } | ||
corpus/ink-edit/native-mouse-over-text/scripts/cold.ps1 created+27| ... | @@ -0,0 +1,27 @@ | ||
| 1 | param([Parameter(Mandatory=$true)][string]$Root, [string]$CloneHost = '') | ||
| 2 | Set-StrictMode -Version Latest | ||
| 3 | $ErrorActionPreference = 'Stop' | ||
| 4 | $root = [IO.Path]::GetFullPath($Root).TrimEnd('\') | ||
| 5 | if ([IO.Path]::GetDirectoryName($root) -ne 'C:\one-tests\runs') { | ||
| 6 | throw 'Choose a run directly below C:\one-tests\runs.' | ||
| 7 | } | ||
| 8 | if (Get-Process ONENOTE -ErrorAction SilentlyContinue) { throw 'Close OneNote before resetting its test cache.' } | ||
| 9 | $key = 'HKCU:\Software\Microsoft\Office\14.0\OneNote' | ||
| 10 | if ($CloneHost) { | ||
| 11 | if ($CloneHost -notmatch '^ONE-[A-Z0-9-]+$' -or [Environment]::MachineName -ne $CloneHost) { | ||
| 12 | throw 'The disposable clone hostname does not match this machine.' | ||
| 13 | } | ||
| 14 | New-Item "$key\Options\Paths" -Force | Out-Null | ||
| 15 | New-ItemProperty "$key\Options\Paths" -Name UnfiledNotesSection -PropertyType ExpandString -Value 'C:\one-tests\Loose.one' -Force | Out-Null | ||
| 16 | } elseif ((Get-ItemProperty "$key\Options\Paths").UnfiledNotesSection -ne 'C:\one-tests\Loose.one' -or | ||
| 17 | -not (Test-Path 'C:\one-tests\profile-original-cache')) { | ||
| 18 | throw 'Park the personal OneNote profile before resetting the test cache.' | ||
| 19 | } | ||
| 20 | $cache = Join-Path $env:LOCALAPPDATA 'Microsoft\OneNote\14.0' | ||
| 21 | $parked = Join-Path 'C:\one-tests\caches' ([IO.Path]::GetFileName($root)) | ||
| 22 | if (Test-Path $parked) { throw 'Choose a new run; its parked cache already exists.' } | ||
| 23 | New-Item -ItemType Directory -Path 'C:\one-tests\caches' -Force | Out-Null | ||
| 24 | if (Test-Path $cache) { Move-Item -LiteralPath $cache -Destination $parked } | ||
| 25 | if (Test-Path "$key\OpenNotebooks") { Remove-Item "$key\OpenNotebooks" -Recurse } | ||
| 26 | New-Item "$key\OpenNotebooks" | Out-Null | ||
| 27 | New-ItemProperty "$key\OpenNotebooks" -Name '1' -PropertyType String -Value "$root\notebook" | Out-Null | ||
corpus/ink-edit/native-mouse-over-text/scripts/read.ps1 created+142| ... | @@ -0,0 +1,142 @@ | ||
| 1 | param( | ||
| 2 | [Parameter(Mandatory=$true)][string]$Root, | ||
| 3 | [int]$ExpectedPages = -1, | ||
| 4 | [switch]$UseCurrentCache, | ||
| 5 | [switch]$Pdf, | ||
| 6 | [switch]$KeepOpen, | ||
| 7 | [string]$CloneHost = '' | ||
| 8 | ) | ||
| 9 | Set-StrictMode -Version Latest | ||
| 10 | $ErrorActionPreference = 'Stop' | ||
| 11 | $root = [IO.Path]::GetFullPath($Root).TrimEnd('\') | ||
| 12 | if ([IO.Path]::GetDirectoryName($root) -ne 'C:\one-tests\runs') { | ||
| 13 | throw 'Choose a run directly below C:\one-tests\runs.' | ||
| 14 | } | ||
| 15 | $notebook = Join-Path $root 'notebook' | ||
| 16 | $output = Join-Path $root 'read' | ||
| 17 | if (Test-Path $output) { throw 'Choose a new read destination.' } | ||
| 18 | if ($UseCurrentCache) { | ||
| 19 | if ((Get-ItemProperty 'HKCU:\Software\Microsoft\Office\14.0\OneNote\Options\Paths').UnfiledNotesSection -ne 'C:\one-tests\Loose.one') { | ||
| 20 | throw 'Park the personal OneNote profile before reading test notebooks.' | ||
| 21 | } | ||
| 22 | } else { | ||
| 23 | & "$PSScriptRoot\cold-current.ps1" -Root $root -CloneHost $CloneHost | ||
| 24 | } | ||
| 25 | New-Item -ItemType Directory -Path $output | Out-Null | ||
| 26 | $app = New-Object -ComObject OneNote.Application | ||
| 27 | $notebookId = '' | ||
| 28 | $failure = $null | ||
| 29 | try { | ||
| 30 | $app.OpenHierarchy($notebook, '', [ref]$notebookId, 0) | ||
| 31 | $process = Get-Process ONENOTE | ||
| 32 | @{ hostname = [Environment]::MachineName; onenote = $process.MainModule.FileVersionInfo.FileVersion; | ||
| 33 | powershell = $PSVersionTable.PSVersion.ToString(); schema = 'xs2010'; cold = (-not $UseCurrentCache.IsPresent) } | | ||
| 34 | ConvertTo-Json | Set-Content (Join-Path $output 'environment.json') -Encoding UTF8 | ||
| 35 | $sections = @() | ||
| 36 | foreach ($file in @(Get-ChildItem $notebook -Recurse | Where-Object { $_.Extension -eq '.one' })) { | ||
| 37 | $id = '' | ||
| 38 | $app.OpenHierarchy($file.FullName, '', [ref]$id, 0) | ||
| 39 | $sections += $id | ||
| 40 | } | ||
| 41 | $deadline = [DateTime]::UtcNow.AddSeconds(300) | ||
| 42 | $previous = '' | ||
| 43 | $lastChange = '' | ||
| 44 | $stableSince = [DateTime]::UtcNow | ||
| 45 | $settled = $false | ||
| 46 | do { | ||
| 47 | $pages = @{} | ||
| 48 | foreach ($section in $sections) { | ||
| 49 | $hierarchy = '' | ||
| 50 | $app.GetHierarchy($section, 4, [ref]$hierarchy, 1) | ||
| 51 | [xml]$xml = $hierarchy | ||
| 52 | foreach ($node in $xml.SelectNodes('//*[@path]')) { | ||
| 53 | if (-not $node.GetAttribute('path').StartsWith("$notebook\", [StringComparison]::OrdinalIgnoreCase)) { | ||
| 54 | throw 'OneNote opened a section outside the copied notebook.' | ||
| 55 | } | ||
| 56 | } | ||
| 57 | foreach ($node in $xml.SelectNodes('//*[local-name()="Page"]')) { | ||
| 58 | $id = $node.GetAttribute('ID') | ||
| 59 | $content = '' | ||
| 60 | $app.GetPageContent($id, [ref]$content, 1, 1) | ||
| 61 | $pages[$id] = $content | ||
| 62 | } | ||
| 63 | } | ||
| 64 | $signature = [String]::Join('|', @($pages.Keys | Sort-Object | ForEach-Object { $_ + $pages[$_] })) | ||
| 65 | if ($signature -ne $previous) { | ||
| 66 | $lastChange = $previous | ||
| 67 | $previous = $signature | ||
| 68 | $stableSince = [DateTime]::UtcNow | ||
| 69 | } | ||
| 70 | if ((($ExpectedPages -ge 0 -and $pages.Count -eq $ExpectedPages) -or | ||
| 71 | ($ExpectedPages -lt 0 -and $pages.Count -gt 0)) -and | ||
| 72 | ([DateTime]::UtcNow - $stableSince).TotalSeconds -ge 2) { $settled = $true; break } | ||
| 73 | Start-Sleep -Milliseconds 250 | ||
| 74 | } while ([DateTime]::UtcNow -lt $deadline) | ||
| 75 | if (-not $settled -or ($ExpectedPages -ge 0 -and $pages.Count -ne $ExpectedPages) -or ($ExpectedPages -lt 0 -and $pages.Count -eq 0)) { | ||
| 76 | [IO.File]::WriteAllText((Join-Path $output 'previous-signature.txt'), $lastChange, [Text.Encoding]::UTF8) | ||
| 77 | $index = 0 | ||
| 78 | foreach ($id in @($pages.Keys | Sort-Object)) { | ||
| 79 | [IO.File]::WriteAllText((Join-Path $output ('unsettled-{0:d3}.xml' -f $index)), $pages[$id], [Text.Encoding]::UTF8) | ||
| 80 | $index++ | ||
| 81 | } | ||
| 82 | $hierarchy = '' | ||
| 83 | $app.GetHierarchy($notebookId, 4, [ref]$hierarchy, 1) | ||
| 84 | [IO.File]::WriteAllText((Join-Path $output 'unsettled-hierarchy.xml'), $hierarchy, [Text.Encoding]::UTF8) | ||
| 85 | throw "Expected $ExpectedPages stable pages; OneNote returned $($pages.Count), settled=$settled." | ||
| 86 | } | ||
| 87 | $index = 0 | ||
| 88 | $payloads = @() | ||
| 89 | foreach ($id in @($pages.Keys | Sort-Object)) { | ||
| 90 | [IO.File]::WriteAllText((Join-Path $output ('page-{0:d3}.xml' -f $index)), $pages[$id], [Text.Encoding]::UTF8) | ||
| 91 | if ($Pdf) { | ||
| 92 | $pdfPath = Join-Path $output ('page-{0:d3}.pdf' -f $index) | ||
| 93 | $app.NavigateTo($id, '', $false) | ||
| 94 | $app.Publish($id, $pdfPath, 3, '') | ||
| 95 | if (-not (Test-Path $pdfPath) -or (Get-Item $pdfPath).Length -eq 0) { throw 'OneNote did not publish the page PDF.' } | ||
| 96 | } | ||
| 97 | [xml]$page = $pages[$id] | ||
| 98 | foreach ($file in $page.SelectNodes('//*[local-name()="InsertedFile" or local-name()="MediaFile"]')) { | ||
| 99 | $bytes = [IO.File]::ReadAllBytes($file.GetAttribute('pathCache')) | ||
| 100 | $hash = [BitConverter]::ToString([Security.Cryptography.SHA256]::Create().ComputeHash($bytes)).Replace('-', '').ToLowerInvariant() | ||
| 101 | [IO.File]::WriteAllBytes((Join-Path $output ($hash + '.attachment')), $bytes) | ||
| 102 | $payloads += @{ page = $id; object = $file.ParentNode.GetAttribute('objectID'); | ||
| 103 | kind = $file.LocalName; name = $file.GetAttribute('preferredName'); | ||
| 104 | sha256 = $hash; bytes = $bytes.Length } | ||
| 105 | } | ||
| 106 | $index++ | ||
| 107 | } | ||
| 108 | [IO.File]::WriteAllText((Join-Path $output 'payloads.json'), (ConvertTo-Json -InputObject $payloads -Depth 4), [Text.Encoding]::UTF8) | ||
| 109 | $all = '' | ||
| 110 | $app.GetHierarchy($notebookId, 4, [ref]$all, 1) | ||
| 111 | [xml]$finalTree = $all | ||
| 112 | $finalIds = @($finalTree.SelectNodes('//*[local-name()="Page"]') | ForEach-Object { $_.GetAttribute('ID') } | Sort-Object -Unique) | ||
| 113 | if ($finalIds.Count -ne $pages.Count -or @($finalIds | Where-Object { -not $pages.ContainsKey($_) }).Count -ne 0) { | ||
| 114 | throw 'The notebook hierarchy changed while collecting page evidence; repeat the cold read.' | ||
| 115 | } | ||
| 116 | [IO.File]::WriteAllText((Join-Path $output 'hierarchy.xml'), $all, [Text.Encoding]::UTF8) | ||
| 117 | Write-Output "Read $($sections.Count) sections and $($pages.Count) pages." | ||
| 118 | } catch { | ||
| 119 | $failure = $_ | ||
| 120 | [IO.File]::WriteAllText((Join-Path $output 'failure.txt'), ($_ | Out-String), [Text.Encoding]::UTF8) | ||
| 121 | throw | ||
| 122 | } finally { | ||
| 123 | try { | ||
| 124 | try { | ||
| 125 | if ($notebookId -and $CloneHost) { $app.SyncHierarchy($notebookId) } | ||
| 126 | if ($notebookId -and -not $KeepOpen -and (-not $UseCurrentCache -or $CloneHost)) { | ||
| 127 | $app.CloseNotebook($notebookId, $false) | ||
| 128 | } | ||
| 129 | } catch { | ||
| 130 | if ($null -eq $failure) { throw } | ||
| 131 | [IO.File]::WriteAllText((Join-Path $output 'cleanup-failure.txt'), ($_ | Out-String), [Text.Encoding]::UTF8) | ||
| 132 | } | ||
| 133 | } finally { | ||
| 134 | [void][Runtime.InteropServices.Marshal]::FinalReleaseComObject($app) | ||
| 135 | } | ||
| 136 | $app = $null | ||
| 137 | [GC]::Collect() | ||
| 138 | [GC]::WaitForPendingFinalizers() | ||
| 139 | if (-not $UseCurrentCache -and -not $CloneHost) { | ||
| 140 | Get-Process ONENOTE -ErrorAction SilentlyContinue | Wait-Process -Timeout 10 | ||
| 141 | } | ||
| 142 | } | ||
corpus/ink-edit/native-mouse-over-text/source.json created+14| ... | @@ -0,0 +1,14 @@ | ||
| 1 | [ | ||
| 2 | { | ||
| 3 | "path": "Open Notebook.onetoc2", | ||
| 4 | "bytes": 1604, | ||
| 5 | "sha256": "6731c4214437fe1cb6aa347b0004542b7f7e4260f47773bf9ec07c4e894de47c", | ||
| 6 | "mtime_ns": 1789206821144296891 | ||
| 7 | }, | ||
| 8 | { | ||
| 9 | "path": "links.one", | ||
| 10 | "bytes": 6128, | ||
| 11 | "sha256": "0cfb356ed374fe919f73fb73fdbe5d66e0baa484d4119f70074d9f869d6db78a", | ||
| 12 | "mtime_ns": 1789206821116200533 | ||
| 13 | } | ||
| 14 | ] | ||
corpus/ink-edit/native-mouse-over-text/teardown.json created+1| ... | @@ -0,0 +1 @@ | ||
| 1 | {"absent": true} | ||
corpus/math-edit/README.md+10| ... | @@ -15,6 +15,16 @@ the stored section and compares it with the exported MathML byte for byte; | ... | @@ -15,6 +15,16 @@ the stored section and compares it with the exported MathML byte for byte; |
| 15 | together with `corpus/m6/native-math-01` (superscripts, a fraction, inline | 15 | together with `corpus/m6/native-math-01` (superscripts, a fraction, inline |
| 16 | Greek) that covers the kinds `onestore::page::Math` claims. | 16 | Greek) that covers the kinds `onestore::page::Math` claims. |
| 17 | 17 | ||
| 18 | `native-editor-2/` is a second session (`ONESTORE_MATH_EQUATIONS` on | ||
| 19 | `tools/native_math.py`): `\sqrt x+1`, `\cbrt(x)`, `\sqrt(n&x)`, `a/b`, | ||
| 20 | `\lim_(x\to 0) f(x)` (the `\lim` stayed literal), `\prod_(k=1)^n k`, | ||
| 21 | `[a+b]`, `\overline(x)` and `x\hat`; `\sqrt (x+1)` again left an empty | ||
| 22 | paragraph. Its MathML adds radicals (25: `msqrt`, or `mroot` with the | ||
| 23 | degree second), a lower limit (19: `munder`, with the upright `lim` as one | ||
| 24 | `mi` in a row), a product, named fences (`mfenced open="[" close="]"`), an | ||
| 25 | overbar (23: `mover accent="false"` with a stretchy bar) and an accent (10: | ||
| 26 | `mover accent="true"` with the combining circumflex exported as `^`). | ||
| 27 | |||
| 18 | `written/candidate` is the page writer's output for | 28 | `written/candidate` is the page writer's output for |
| 19 | `equations_are_written_and_read_back`: on a fresh section, every native | 29 | `equations_are_written_and_read_back`: on a fresh section, every native |
| 20 | equation of both fixtures copied as a new paragraph, then two expressions | 30 | equation of both fixtures copied as a new paragraph, then two expressions |
corpus/math-edit/native-editor-2/before-read/environment.json created+7| ... | @@ -0,0 +1,7 @@ | ||
| 1 | { | ||
| 2 | "powershell": "5.1.14409.1005", | ||
| 3 | "schema": "xs2010", | ||
| 4 | "hostname": "ONE-M6-C31DFB94", | ||
| 5 | "cold": true, | ||
| 6 | "onenote": "14.0.4763.1000" | ||
| 7 | } | ||
corpus/math-edit/native-editor-2/before-read/hierarchy.xml created+2| ... | @@ -0,0 +1,2 @@ | ||
| 1 | <?xml version="1.0"?> | ||
| 2 | <one:Notebook xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" name="notebook" nickname="notebook" ID="{92C524E4-323B-4FB6-A579-63EE80B74012}{1}{B0}" path="C:\one-tests\runs\capture\notebook" lastModifiedTime="2026-09-13T03:07:43.000Z" color="#B49EDE"><one:Section name="links" ID="{894B3830-739E-0935-36CF-A0E48027D776}{1}{B0}" path="C:\one-tests\runs\capture\notebook\links.one" lastModifiedTime="2026-09-13T03:07:43.000Z" color="#8AA8E4"><one:Page ID="{2846FEB9-1B89-0608-2064-808478C45E8D}{1}{B0}" name="Read about Rust HYPERLINK &quot;https://example.invalid/rust&quot;the Rust site" dateTime="2026-09-12T09:49:16.000Z" lastModifiedTime="2026-09-13T03:07:43.000Z" pageLevel="1"/></one:Section></one:Notebook> | ||
corpus/math-edit/native-editor-2/before-read/page-000.xml created+3| ... | @@ -0,0 +1,3 @@ | ||
| 1 | <?xml version="1.0"?> | ||
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{2846FEB9-1B89-0608-2064-808478C45E8D}{1}{B0}" name="Read about Rust HYPERLINK &quot;https://example.invalid/rust&quot;the Rust site" dateTime="2026-09-12T09:49:16.000Z" lastModifiedTime="2026-09-13T03:07:43.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-12T09:49:16.000Z" objectID="{894B3830-739E-0935-36CF-A0E48027D776}{23}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="198.3904571533203" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-12T09:49:16.000Z" lastModifiedTime="2026-09-12T09:49:16.000Z" objectID="{894B3830-739E-0935-36CF-A0E48027D776}{24}{B0}" alignment="left"><one:T><![CDATA[Read about Rust <a | ||
| 3 | href="https://example.invalid/rust">the Rust site</a>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> | ||
corpus/math-edit/native-editor-2/before-read/payloads.json created+3| ... | @@ -0,0 +1,3 @@ | ||
| 1 | [ | ||
| 2 | |||
| 3 | ] | ||
| \ No newline at end of file | |||
corpus/math-edit/native-editor-2/commands.jsonl created+6| ... | @@ -0,0 +1,6 @@ | ||
| 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-C31DFB94 -ExpectedPages 1 -KeepOpen", "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} | ||
| 4 | {"command": "powershell -NoProfile -Command \"Rename-Item C:\\one-tests\\runs\\capture\\read before-read\"", "exit": 0, "stdout": "", "stderr": "", "error": null} | ||
| 5 | {"command": "powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File C:\\one-tests\\read-current.ps1 -Root C:\\one-tests\\runs\\capture -CloneHost ONE-M6-C31DFB94 -UseCurrentCache -ExpectedPages -1", "exit": 0, "stdout": "Read 1 sections and 1 pages.\r\n", "stderr": "", "error": null} | ||
| 6 | {"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/math-edit/native-editor-2/equations.ahk created+104| ... | @@ -0,0 +1,104 @@ | ||
| 1 | ComObject("OneNote.Application").NavigateTo("{2846FEB9-1B89-0608-2064-808478C45E8D}{1}{B0}", "", false) | ||
| 2 | WinWait("ahk_exe ONENOTE.EXE", , 30) | ||
| 3 | WinActivate("ahk_exe ONENOTE.EXE") | ||
| 4 | WinWaitActive("ahk_exe ONENOTE.EXE", , 30) | ||
| 5 | WinMaximize("ahk_exe ONENOTE.EXE") | ||
| 6 | Sleep(3000) | ||
| 7 | CoordMode("Mouse", "Screen") | ||
| 8 | Click(300, 140) | ||
| 9 | Sleep(500) | ||
| 10 | Send("{End}") | ||
| 11 | Sleep(300) | ||
| 12 | Send("{Enter}") | ||
| 13 | Sleep(500) | ||
| 14 | Send("!=") | ||
| 15 | Sleep(600) | ||
| 16 | SendText("\sqrt x+1") | ||
| 17 | Send(" ") | ||
| 18 | Sleep(600) | ||
| 19 | Send("{End}") | ||
| 20 | Sleep(300) | ||
| 21 | Send("{Enter}") | ||
| 22 | Sleep(600) | ||
| 23 | Send("!=") | ||
| 24 | Sleep(600) | ||
| 25 | SendText("\sqrt (x+1)") | ||
| 26 | Send(" ") | ||
| 27 | Sleep(600) | ||
| 28 | Send("{End}") | ||
| 29 | Sleep(300) | ||
| 30 | Send("{Enter}") | ||
| 31 | Sleep(600) | ||
| 32 | Send("!=") | ||
| 33 | Sleep(600) | ||
| 34 | SendText("\cbrt(x)") | ||
| 35 | Send(" ") | ||
| 36 | Sleep(600) | ||
| 37 | Send("{End}") | ||
| 38 | Sleep(300) | ||
| 39 | Send("{Enter}") | ||
| 40 | Sleep(600) | ||
| 41 | Send("!=") | ||
| 42 | Sleep(600) | ||
| 43 | SendText("\sqrt(n&x)") | ||
| 44 | Send(" ") | ||
| 45 | Sleep(600) | ||
| 46 | Send("{End}") | ||
| 47 | Sleep(300) | ||
| 48 | Send("{Enter}") | ||
| 49 | Sleep(600) | ||
| 50 | Send("!=") | ||
| 51 | Sleep(600) | ||
| 52 | SendText("a/b") | ||
| 53 | Send(" ") | ||
| 54 | Sleep(600) | ||
| 55 | Send("{End}") | ||
| 56 | Sleep(300) | ||
| 57 | Send("{Enter}") | ||
| 58 | Sleep(600) | ||
| 59 | Send("!=") | ||
| 60 | Sleep(600) | ||
| 61 | SendText("\lim_(x\to 0) f(x)") | ||
| 62 | Send(" ") | ||
| 63 | Sleep(600) | ||
| 64 | Send("{End}") | ||
| 65 | Sleep(300) | ||
| 66 | Send("{Enter}") | ||
| 67 | Sleep(600) | ||
| 68 | Send("!=") | ||
| 69 | Sleep(600) | ||
| 70 | SendText("\prod_(k=1)^n k") | ||
| 71 | Send(" ") | ||
| 72 | Sleep(600) | ||
| 73 | Send("{End}") | ||
| 74 | Sleep(300) | ||
| 75 | Send("{Enter}") | ||
| 76 | Sleep(600) | ||
| 77 | Send("!=") | ||
| 78 | Sleep(600) | ||
| 79 | SendText("[a+b]") | ||
| 80 | Send(" ") | ||
| 81 | Sleep(600) | ||
| 82 | Send("{End}") | ||
| 83 | Sleep(300) | ||
| 84 | Send("{Enter}") | ||
| 85 | Sleep(600) | ||
| 86 | Send("!=") | ||
| 87 | Sleep(600) | ||
| 88 | SendText("\overline(x)") | ||
| 89 | Send(" ") | ||
| 90 | Sleep(600) | ||
| 91 | Send("{End}") | ||
| 92 | Sleep(300) | ||
| 93 | Send("{Enter}") | ||
| 94 | Sleep(600) | ||
| 95 | Send("!=") | ||
| 96 | Sleep(600) | ||
| 97 | SendText("x\hat") | ||
| 98 | Send(" ") | ||
| 99 | Sleep(600) | ||
| 100 | Send("{End}") | ||
| 101 | Sleep(300) | ||
| 102 | Send("{Enter}") | ||
| 103 | Sleep(600) | ||
| 104 | Sleep(2000) | ||
corpus/math-edit/native-editor-2/equations.json created+13| ... | @@ -0,0 +1,13 @@ | ||
| 1 | { | ||
| 2 | "exit": 0, | ||
| 3 | "stdout": "", | ||
| 4 | "stderr": "", | ||
| 5 | "w": 800, | ||
| 6 | "h": 600, | ||
| 7 | "error": null, | ||
| 8 | "win": { | ||
| 9 | "title": "Read about Rust the Rust site - Microsoft OneNote", | ||
| 10 | "class": "Framework::CFrame", | ||
| 11 | "dialog": false | ||
| 12 | } | ||
| 13 | } | ||
| \ No newline at end of file | |||
corpus/math-edit/native-editor-2/equations.png created| Binary files /dev/null and b/corpus/math-edit/native-editor-2/equations.png differ | |||
corpus/math-edit/native-editor-2/machine.json created+1| ... | @@ -0,0 +1 @@ | ||
| 1 | {"name": "m6-c31dfb94", "hostname": "ONE-M6-C31DFB94"} | ||
corpus/math-edit/native-editor-2/notebook/Open Notebook.onetoc2 created| Binary files /dev/null and b/corpus/math-edit/native-editor-2/notebook/Open Notebook.onetoc2 differ | |||
corpus/math-edit/native-editor-2/notebook/links.one created| Binary files /dev/null and b/corpus/math-edit/native-editor-2/notebook/links.one differ | |||
corpus/math-edit/native-editor-2/read/environment.json created+7| ... | @@ -0,0 +1,7 @@ | ||
| 1 | { | ||
| 2 | "powershell": "5.1.14409.1005", | ||
| 3 | "schema": "xs2010", | ||
| 4 | "hostname": "ONE-M6-C31DFB94", | ||
| 5 | "cold": false, | ||
| 6 | "onenote": "14.0.4763.1000" | ||
| 7 | } | ||
corpus/math-edit/native-editor-2/read/hierarchy.xml created+2| ... | @@ -0,0 +1,2 @@ | ||
| 1 | <?xml version="1.0"?> | ||
| 2 | <one:Notebook xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" name="notebook" nickname="notebook" ID="{92C524E4-323B-4FB6-A579-63EE80B74012}{1}{B0}" path="C:\one-tests\runs\capture\notebook" lastModifiedTime="2026-09-13T03:09:47.000Z" color="#B49EDE" isCurrentlyViewed="true"><one:Section name="links" ID="{894B3830-739E-0935-36CF-A0E48027D776}{1}{B0}" path="C:\one-tests\runs\capture\notebook\links.one" lastModifiedTime="2026-09-13T03:09:47.000Z" color="#8AA8E4" isCurrentlyViewed="true"><one:Page ID="{2846FEB9-1B89-0608-2064-808478C45E8D}{1}{B0}" name="Read about Rust the Rust site" dateTime="2026-09-12T09:49:16.000Z" lastModifiedTime="2026-09-13T03:09:47.000Z" pageLevel="1" isCurrentlyViewed="true"/></one:Section></one:Notebook> | ||
corpus/math-edit/native-editor-2/read/page-000.xml created+3| ... | @@ -0,0 +1,3 @@ | ||
| 1 | <?xml version="1.0"?> | ||
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{2846FEB9-1B89-0608-2064-808478C45E8D}{1}{B0}" name="Read about Rust the Rust site" dateTime="2026-09-12T09:49:16.000Z" lastModifiedTime="2026-09-13T03:09:47.000Z" pageLevel="1" isCurrentlyViewed="true" 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="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-13T03:09:47.000Z" objectID="{894B3830-739E-0935-36CF-A0E48027D776}{23}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="198.3904571533203" height="194.8564453125"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-12T09:49:16.000Z" lastModifiedTime="2026-09-13T03:09:22.000Z" objectID="{894B3830-739E-0935-36CF-A0E48027D776}{24}{B0}" alignment="left" style="font-family:Calibri;font-size:11.0pt"><one:T><![CDATA[Read about Rust <a | ||
| 3 | href="https://example.invalid/rust">the Rust site</a>]]></one:T></one:OE><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-13T03:09:27.000Z" lastModifiedTime="2026-09-13T03:09:28.000Z" objectID="{57D6F377-4FCE-4238-B8CA-B4984E7AF8F2}{12}{B0}" alignment="left" quickStyleIndex="0" 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:msqrt><mml:mi>x</mml:mi></mml:msqrt><mml:mo>+</mml:mo><mml:mn>1</mml:mn><mml:mo>&nbsp;</mml:mo></mml:math><![endif]-->]]></one:T></one:OE><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-13T03:09:28.000Z" lastModifiedTime="2026-09-13T03:09:28.000Z" objectID="{57D6F377-4FCE-4238-B8CA-B4984E7AF8F2}{18}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[]]></one:T></one:OE><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-13T03:09:31.000Z" lastModifiedTime="2026-09-13T03:09:32.000Z" objectID="{57D6F377-4FCE-4238-B8CA-B4984E7AF8F2}{21}{B0}" alignment="left" quickStyleIndex="0" 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:mroot><mml:mi>x</mml:mi><mml:mn>3</mml:mn></mml:mroot></mml:math><![endif]-->]]></one:T></one:OE><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-13T03:09:33.000Z" lastModifiedTime="2026-09-13T03:09:34.000Z" objectID="{57D6F377-4FCE-4238-B8CA-B4984E7AF8F2}{24}{B0}" alignment="left" quickStyleIndex="0" 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:mroot><mml:mi>x</mml:mi><mml:mi>n</mml:mi></mml:mroot></mml:math><![endif]-->]]></one:T></one:OE><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-13T03:09:34.000Z" lastModifiedTime="2026-09-13T03:09:36.000Z" objectID="{57D6F377-4FCE-4238-B8CA-B4984E7AF8F2}{27}{B0}" alignment="left" quickStyleIndex="0" 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:mi>a</mml:mi><mml:mi>b</mml:mi></mml:mfrac></mml:math><![endif]-->]]></one:T></one:OE><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-13T03:09:37.000Z" lastModifiedTime="2026-09-13T03:09:38.000Z" objectID="{57D6F377-4FCE-4238-B8CA-B4984E7AF8F2}{30}{B0}" alignment="left" quickStyleIndex="0" 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:mo>\</mml:mo><mml:munder><mml:mrow><mml:mi>lim</mml:mi></mml:mrow><mml:mrow><mml:mi>x</mml:mi><mml:mo>→</mml:mo><mml:mn>0</mml:mn></mml:mrow></mml:munder><mml:mi>f</mml:mi><mml:mfenced><mml:mi>x</mml:mi></mml:mfenced></mml:math><![endif]-->]]></one:T></one:OE><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-13T03:09:39.000Z" lastModifiedTime="2026-09-13T03:09:40.000Z" objectID="{57D6F377-4FCE-4238-B8CA-B4984E7AF8F2}{34}{B0}" alignment="left" quickStyleIndex="0" 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>k</mml:mi><mml:mo>=</mml:mo><mml:mn>1</mml:mn></mml:mrow><mml:mi>n</mml:mi></mml:munderover><mml:mrow><mml:mi>k</mml:mi><mml:mo>&nbsp;</mml:mo></mml:mrow></mml:math><![endif]-->]]></one:T></one:OE><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-13T03:09:41.000Z" lastModifiedTime="2026-09-13T03:09:42.000Z" objectID="{57D6F377-4FCE-4238-B8CA-B4984E7AF8F2}{37}{B0}" alignment="left" quickStyleIndex="0" 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 open="[" close="]"><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 author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-13T03:09:44.000Z" lastModifiedTime="2026-09-13T03:09:44.000Z" objectID="{57D6F377-4FCE-4238-B8CA-B4984E7AF8F2}{40}{B0}" alignment="left" quickStyleIndex="0" 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:mover accent="false"><mml:mi>x</mml:mi><mml:mo stretchy="true">¯</mml:mo></mml:mover></mml:math><![endif]-->]]></one:T></one:OE><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-13T03:09:45.000Z" lastModifiedTime="2026-09-13T03:09:47.000Z" objectID="{57D6F377-4FCE-4238-B8CA-B4984E7AF8F2}{43}{B0}" alignment="left" quickStyleIndex="0" 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:mover accent="true"><mml:mi>x</mml:mi><mml:mo>^</mml:mo></mml:mover></mml:math><![endif]-->]]></one:T></one:OE><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-13T03:09:47.000Z" lastModifiedTime="2026-09-13T03:09:47.000Z" objectID="{57D6F377-4FCE-4238-B8CA-B4984E7AF8F2}{46}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> | ||
corpus/math-edit/native-editor-2/read/payloads.json created+3| ... | @@ -0,0 +1,3 @@ | ||
| 1 | [ | ||
| 2 | |||
| 3 | ] | ||
| \ No newline at end of file | |||
corpus/math-edit/native-editor-2/run.json created+18| ... | @@ -0,0 +1,18 @@ | ||
| 1 | { | ||
| 2 | "notebook": "/Users/clo/dev/one/corpus/link-edit/candidate", | ||
| 3 | "expected_pages": 1, | ||
| 4 | "author": null, | ||
| 5 | "author_timeout_seconds": 600, | ||
| 6 | "inspect": false, | ||
| 7 | "collect_notebook": true, | ||
| 8 | "base": { | ||
| 9 | "file": "win7-office-base.qcow2", | ||
| 10 | "format": "qcow2", | ||
| 11 | "sha256": "a1a4f8fab782ee14885ff801ca2f6347c208fdcfc3513637096f314315c89346", | ||
| 12 | "virtual_size": 68719476736 | ||
| 13 | }, | ||
| 14 | "scripts": { | ||
| 15 | "cold.ps1": "c177fc72ae6c2634d186f5671a880de20b09f9716532c37c69cb13aa15c7e331", | ||
| 16 | "read.ps1": "04013bfcccee40a17e2a225f9b9e96f40a3a8daad9e350609654f8eda3ccbb41" | ||
| 17 | } | ||
| 18 | } | ||
corpus/math-edit/native-editor-2/scripts/cold.ps1 created+27| ... | @@ -0,0 +1,27 @@ | ||
| 1 | param([Parameter(Mandatory=$true)][string]$Root, [string]$CloneHost = '') | ||
| 2 | Set-StrictMode -Version Latest | ||
| 3 | $ErrorActionPreference = 'Stop' | ||
| 4 | $root = [IO.Path]::GetFullPath($Root).TrimEnd('\') | ||
| 5 | if ([IO.Path]::GetDirectoryName($root) -ne 'C:\one-tests\runs') { | ||
| 6 | throw 'Choose a run directly below C:\one-tests\runs.' | ||
| 7 | } | ||
| 8 | if (Get-Process ONENOTE -ErrorAction SilentlyContinue) { throw 'Close OneNote before resetting its test cache.' } | ||
| 9 | $key = 'HKCU:\Software\Microsoft\Office\14.0\OneNote' | ||
| 10 | if ($CloneHost) { | ||
| 11 | if ($CloneHost -notmatch '^ONE-[A-Z0-9-]+$' -or [Environment]::MachineName -ne $CloneHost) { | ||
| 12 | throw 'The disposable clone hostname does not match this machine.' | ||
| 13 | } | ||
| 14 | New-Item "$key\Options\Paths" -Force | Out-Null | ||
| 15 | New-ItemProperty "$key\Options\Paths" -Name UnfiledNotesSection -PropertyType ExpandString -Value 'C:\one-tests\Loose.one' -Force | Out-Null | ||
| 16 | } elseif ((Get-ItemProperty "$key\Options\Paths").UnfiledNotesSection -ne 'C:\one-tests\Loose.one' -or | ||
| 17 | -not (Test-Path 'C:\one-tests\profile-original-cache')) { | ||
| 18 | throw 'Park the personal OneNote profile before resetting the test cache.' | ||
| 19 | } | ||
| 20 | $cache = Join-Path $env:LOCALAPPDATA 'Microsoft\OneNote\14.0' | ||
| 21 | $parked = Join-Path 'C:\one-tests\caches' ([IO.Path]::GetFileName($root)) | ||
| 22 | if (Test-Path $parked) { throw 'Choose a new run; its parked cache already exists.' } | ||
| 23 | New-Item -ItemType Directory -Path 'C:\one-tests\caches' -Force | Out-Null | ||
| 24 | if (Test-Path $cache) { Move-Item -LiteralPath $cache -Destination $parked } | ||
| 25 | if (Test-Path "$key\OpenNotebooks") { Remove-Item "$key\OpenNotebooks" -Recurse } | ||
| 26 | New-Item "$key\OpenNotebooks" | Out-Null | ||
| 27 | New-ItemProperty "$key\OpenNotebooks" -Name '1' -PropertyType String -Value "$root\notebook" | Out-Null | ||
corpus/math-edit/native-editor-2/scripts/read.ps1 created+142| ... | @@ -0,0 +1,142 @@ | ||
| 1 | param( | ||
| 2 | [Parameter(Mandatory=$true)][string]$Root, | ||
| 3 | [int]$ExpectedPages = -1, | ||
| 4 | [switch]$UseCurrentCache, | ||
| 5 | [switch]$Pdf, | ||
| 6 | [switch]$KeepOpen, | ||
| 7 | [string]$CloneHost = '' | ||
| 8 | ) | ||
| 9 | Set-StrictMode -Version Latest | ||
| 10 | $ErrorActionPreference = 'Stop' | ||
| 11 | $root = [IO.Path]::GetFullPath($Root).TrimEnd('\') | ||
| 12 | if ([IO.Path]::GetDirectoryName($root) -ne 'C:\one-tests\runs') { | ||
| 13 | throw 'Choose a run directly below C:\one-tests\runs.' | ||
| 14 | } | ||
| 15 | $notebook = Join-Path $root 'notebook' | ||
| 16 | $output = Join-Path $root 'read' | ||
| 17 | if (Test-Path $output) { throw 'Choose a new read destination.' } | ||
| 18 | if ($UseCurrentCache) { | ||
| 19 | if ((Get-ItemProperty 'HKCU:\Software\Microsoft\Office\14.0\OneNote\Options\Paths').UnfiledNotesSection -ne 'C:\one-tests\Loose.one') { | ||
| 20 | throw 'Park the personal OneNote profile before reading test notebooks.' | ||
| 21 | } | ||
| 22 | } else { | ||
| 23 | & "$PSScriptRoot\cold-current.ps1" -Root $root -CloneHost $CloneHost | ||
| 24 | } | ||
| 25 | New-Item -ItemType Directory -Path $output | Out-Null | ||
| 26 | $app = New-Object -ComObject OneNote.Application | ||
| 27 | $notebookId = '' | ||
| 28 | $failure = $null | ||
| 29 | try { | ||
| 30 | $app.OpenHierarchy($notebook, '', [ref]$notebookId, 0) | ||
| 31 | $process = Get-Process ONENOTE | ||
| 32 | @{ hostname = [Environment]::MachineName; onenote = $process.MainModule.FileVersionInfo.FileVersion; | ||
| 33 | powershell = $PSVersionTable.PSVersion.ToString(); schema = 'xs2010'; cold = (-not $UseCurrentCache.IsPresent) } | | ||
| 34 | ConvertTo-Json | Set-Content (Join-Path $output 'environment.json') -Encoding UTF8 | ||
| 35 | $sections = @() | ||
| 36 | foreach ($file in @(Get-ChildItem $notebook -Recurse | Where-Object { $_.Extension -eq '.one' })) { | ||
| 37 | $id = '' | ||
| 38 | $app.OpenHierarchy($file.FullName, '', [ref]$id, 0) | ||
| 39 | $sections += $id | ||
| 40 | } | ||
| 41 | $deadline = [DateTime]::UtcNow.AddSeconds(300) | ||
| 42 | $previous = '' | ||
| 43 | $lastChange = '' | ||
| 44 | $stableSince = [DateTime]::UtcNow | ||
| 45 | $settled = $false | ||
| 46 | do { | ||
| 47 | $pages = @{} | ||
| 48 | foreach ($section in $sections) { | ||
| 49 | $hierarchy = '' | ||
| 50 | $app.GetHierarchy($section, 4, [ref]$hierarchy, 1) | ||
| 51 | [xml]$xml = $hierarchy | ||
| 52 | foreach ($node in $xml.SelectNodes('//*[@path]')) { | ||
| 53 | if (-not $node.GetAttribute('path').StartsWith("$notebook\", [StringComparison]::OrdinalIgnoreCase)) { | ||
| 54 | throw 'OneNote opened a section outside the copied notebook.' | ||
| 55 | } | ||
| 56 | } | ||
| 57 | foreach ($node in $xml.SelectNodes('//*[local-name()="Page"]')) { | ||
| 58 | $id = $node.GetAttribute('ID') | ||
| 59 | $content = '' | ||
| 60 | $app.GetPageContent($id, [ref]$content, 1, 1) | ||
| 61 | $pages[$id] = $content | ||
| 62 | } | ||
| 63 | } | ||
| 64 | $signature = [String]::Join('|', @($pages.Keys | Sort-Object | ForEach-Object { $_ + $pages[$_] })) | ||
| 65 | if ($signature -ne $previous) { | ||
| 66 | $lastChange = $previous | ||
| 67 | $previous = $signature | ||
| 68 | $stableSince = [DateTime]::UtcNow | ||
| 69 | } | ||
| 70 | if ((($ExpectedPages -ge 0 -and $pages.Count -eq $ExpectedPages) -or | ||
| 71 | ($ExpectedPages -lt 0 -and $pages.Count -gt 0)) -and | ||
| 72 | ([DateTime]::UtcNow - $stableSince).TotalSeconds -ge 2) { $settled = $true; break } | ||
| 73 | Start-Sleep -Milliseconds 250 | ||
| 74 | } while ([DateTime]::UtcNow -lt $deadline) | ||
| 75 | if (-not $settled -or ($ExpectedPages -ge 0 -and $pages.Count -ne $ExpectedPages) -or ($ExpectedPages -lt 0 -and $pages.Count -eq 0)) { | ||
| 76 | [IO.File]::WriteAllText((Join-Path $output 'previous-signature.txt'), $lastChange, [Text.Encoding]::UTF8) | ||
| 77 | $index = 0 | ||
| 78 | foreach ($id in @($pages.Keys | Sort-Object)) { | ||
| 79 | [IO.File]::WriteAllText((Join-Path $output ('unsettled-{0:d3}.xml' -f $index)), $pages[$id], [Text.Encoding]::UTF8) | ||
| 80 | $index++ | ||
| 81 | } | ||
| 82 | $hierarchy = '' | ||
| 83 | $app.GetHierarchy($notebookId, 4, [ref]$hierarchy, 1) | ||
| 84 | [IO.File]::WriteAllText((Join-Path $output 'unsettled-hierarchy.xml'), $hierarchy, [Text.Encoding]::UTF8) | ||
| 85 | throw "Expected $ExpectedPages stable pages; OneNote returned $($pages.Count), settled=$settled." | ||
| 86 | } | ||
| 87 | $index = 0 | ||
| 88 | $payloads = @() | ||
| 89 | foreach ($id in @($pages.Keys | Sort-Object)) { | ||
| 90 | [IO.File]::WriteAllText((Join-Path $output ('page-{0:d3}.xml' -f $index)), $pages[$id], [Text.Encoding]::UTF8) | ||
| 91 | if ($Pdf) { | ||
| 92 | $pdfPath = Join-Path $output ('page-{0:d3}.pdf' -f $index) | ||
| 93 | $app.NavigateTo($id, '', $false) | ||
| 94 | $app.Publish($id, $pdfPath, 3, '') | ||
| 95 | if (-not (Test-Path $pdfPath) -or (Get-Item $pdfPath).Length -eq 0) { throw 'OneNote did not publish the page PDF.' } | ||
| 96 | } | ||
| 97 | [xml]$page = $pages[$id] | ||
| 98 | foreach ($file in $page.SelectNodes('//*[local-name()="InsertedFile" or local-name()="MediaFile"]')) { | ||
| 99 | $bytes = [IO.File]::ReadAllBytes($file.GetAttribute('pathCache')) | ||
| 100 | $hash = [BitConverter]::ToString([Security.Cryptography.SHA256]::Create().ComputeHash($bytes)).Replace('-', '').ToLowerInvariant() | ||
| 101 | [IO.File]::WriteAllBytes((Join-Path $output ($hash + '.attachment')), $bytes) | ||
| 102 | $payloads += @{ page = $id; object = $file.ParentNode.GetAttribute('objectID'); | ||
| 103 | kind = $file.LocalName; name = $file.GetAttribute('preferredName'); | ||
| 104 | sha256 = $hash; bytes = $bytes.Length } | ||
| 105 | } | ||
| 106 | $index++ | ||
| 107 | } | ||
| 108 | [IO.File]::WriteAllText((Join-Path $output 'payloads.json'), (ConvertTo-Json -InputObject $payloads -Depth 4), [Text.Encoding]::UTF8) | ||
| 109 | $all = '' | ||
| 110 | $app.GetHierarchy($notebookId, 4, [ref]$all, 1) | ||
| 111 | [xml]$finalTree = $all | ||
| 112 | $finalIds = @($finalTree.SelectNodes('//*[local-name()="Page"]') | ForEach-Object { $_.GetAttribute('ID') } | Sort-Object -Unique) | ||
| 113 | if ($finalIds.Count -ne $pages.Count -or @($finalIds | Where-Object { -not $pages.ContainsKey($_) }).Count -ne 0) { | ||
| 114 | throw 'The notebook hierarchy changed while collecting page evidence; repeat the cold read.' | ||
| 115 | } | ||
| 116 | [IO.File]::WriteAllText((Join-Path $output 'hierarchy.xml'), $all, [Text.Encoding]::UTF8) | ||
| 117 | Write-Output "Read $($sections.Count) sections and $($pages.Count) pages." | ||
| 118 | } catch { | ||
| 119 | $failure = $_ | ||
| 120 | [IO.File]::WriteAllText((Join-Path $output 'failure.txt'), ($_ | Out-String), [Text.Encoding]::UTF8) | ||
| 121 | throw | ||
| 122 | } finally { | ||
| 123 | try { | ||
| 124 | try { | ||
| 125 | if ($notebookId -and $CloneHost) { $app.SyncHierarchy($notebookId) } | ||
| 126 | if ($notebookId -and -not $KeepOpen -and (-not $UseCurrentCache -or $CloneHost)) { | ||
| 127 | $app.CloseNotebook($notebookId, $false) | ||
| 128 | } | ||
| 129 | } catch { | ||
| 130 | if ($null -eq $failure) { throw } | ||
| 131 | [IO.File]::WriteAllText((Join-Path $output 'cleanup-failure.txt'), ($_ | Out-String), [Text.Encoding]::UTF8) | ||
| 132 | } | ||
| 133 | } finally { | ||
| 134 | [void][Runtime.InteropServices.Marshal]::FinalReleaseComObject($app) | ||
| 135 | } | ||
| 136 | $app = $null | ||
| 137 | [GC]::Collect() | ||
| 138 | [GC]::WaitForPendingFinalizers() | ||
| 139 | if (-not $UseCurrentCache -and -not $CloneHost) { | ||
| 140 | Get-Process ONENOTE -ErrorAction SilentlyContinue | Wait-Process -Timeout 10 | ||
| 141 | } | ||
| 142 | } | ||
corpus/math-edit/native-editor-2/source.json created+14| ... | @@ -0,0 +1,14 @@ | ||
| 1 | [ | ||
| 2 | { | ||
| 3 | "path": "Open Notebook.onetoc2", | ||
| 4 | "bytes": 1604, | ||
| 5 | "sha256": "6731c4214437fe1cb6aa347b0004542b7f7e4260f47773bf9ec07c4e894de47c", | ||
| 6 | "mtime_ns": 1789206821144296891 | ||
| 7 | }, | ||
| 8 | { | ||
| 9 | "path": "links.one", | ||
| 10 | "bytes": 6128, | ||
| 11 | "sha256": "0cfb356ed374fe919f73fb73fdbe5d66e0baa484d4119f70074d9f869d6db78a", | ||
| 12 | "mtime_ns": 1789206821116200533 | ||
| 13 | } | ||
| 14 | ] | ||
corpus/math-edit/native-editor-2/teardown.json created+1| ... | @@ -0,0 +1 @@ | ||
| 1 | {"absent": true} | ||
crates/onestore/src/page/math.rs+76-4| ... | @@ -11,6 +11,8 @@ use crate::document::MathObject; | ... | @@ -11,6 +11,8 @@ use crate::document::MathObject; |
| 11 | #[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] | 11 | #[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] |
| 12 | pub enum Math { | 12 | pub enum Math { |
| 13 | Identifier(char), | 13 | Identifier(char), |
| 14 | /// Upright letters, as the editor stores a function name such as `lim`. | ||
| 15 | Function(String), | ||
| 14 | Number(String), | 16 | Number(String), |
| 15 | Operator(char), | 17 | Operator(char), |
| 16 | Object { | 18 | Object { |
| ... | @@ -51,6 +53,7 @@ impl Math { | ... | @@ -51,6 +53,7 @@ impl Math { |
| 51 | let mut frames: Vec<Frame> = Vec::new(); | 53 | let mut frames: Vec<Frame> = Vec::new(); |
| 52 | let mut sequences: Vec<Vec<Math>> = vec![Vec::new()]; | 54 | let mut sequences: Vec<Vec<Math>> = vec![Vec::new()]; |
| 53 | let mut number = String::new(); | 55 | let mut number = String::new(); |
| 56 | let mut word = String::new(); | ||
| 54 | let mut offset = 0; | 57 | let mut offset = 0; |
| 55 | for character in paragraph.text().chars() { | 58 | for character in paragraph.text().chars() { |
| 56 | let here = offset; | 59 | let here = offset; |
| ... | @@ -63,6 +66,14 @@ impl Math { | ... | @@ -63,6 +66,14 @@ impl Math { |
| 63 | let number = std::mem::take(&mut number); | 66 | let number = std::mem::take(&mut number); |
| 64 | sequences.last_mut().unwrap().push(Math::Number(number)); | 67 | sequences.last_mut().unwrap().push(Math::Number(number)); |
| 65 | } | 68 | } |
| 69 | if character.is_ascii_alphabetic() { | ||
| 70 | word.push(character); | ||
| 71 | continue; | ||
| 72 | } | ||
| 73 | if !word.is_empty() { | ||
| 74 | let word = std::mem::take(&mut word); | ||
| 75 | sequences.last_mut().unwrap().push(Math::Function(word)); | ||
| 76 | } | ||
| 66 | match character { | 77 | match character { |
| 67 | OBJECT_START => { | 78 | OBJECT_START => { |
| 68 | // The span lookup treats a boundary offset as the span ending there, so the | 79 | // The span lookup treats a boundary offset as the span ending there, so the |
| ... | @@ -108,6 +119,9 @@ impl Math { | ... | @@ -108,6 +119,9 @@ impl Math { |
| 108 | if !number.is_empty() { | 119 | if !number.is_empty() { |
| 109 | sequences.last_mut().unwrap().push(Math::Number(number)); | 120 | sequences.last_mut().unwrap().push(Math::Number(number)); |
| 110 | } | 121 | } |
| 122 | if !word.is_empty() { | ||
| 123 | sequences.last_mut().unwrap().push(Math::Function(word)); | ||
| 124 | } | ||
| 111 | if !frames.is_empty() { | 125 | if !frames.is_empty() { |
| 112 | return Err(invalid("An equation object never ends")); | 126 | return Err(invalid("An equation object never ends")); |
| 113 | } | 127 | } |
| ... | @@ -127,6 +141,7 @@ impl Math { | ... | @@ -127,6 +141,7 @@ impl Math { |
| 127 | fn write(&self, out: &mut String) { | 141 | fn write(&self, out: &mut String) { |
| 128 | match self { | 142 | match self { |
| 129 | Math::Identifier(c) => tag(out, "mi", &c.to_string()), | 143 | Math::Identifier(c) => tag(out, "mi", &c.to_string()), |
| 144 | Math::Function(name) => tag(out, "mi", name), | ||
| 130 | Math::Number(n) => tag(out, "mn", n), | 145 | Math::Number(n) => tag(out, "mn", n), |
| 131 | Math::Operator(c) => tag(out, "mo", &c.to_string()), | 146 | Math::Operator(c) => tag(out, "mo", &c.to_string()), |
| 132 | Math::Object { | 147 | Math::Object { |
| ... | @@ -134,12 +149,16 @@ impl Math { | ... | @@ -134,12 +149,16 @@ impl Math { |
| 134 | symbols, | 149 | symbols, |
| 135 | arguments, | 150 | arguments, |
| 136 | } => { | 151 | } => { |
| 137 | // A lone identifier, number or operator stands bare; anything else is a row. | 152 | // A lone letter, number or operator stands bare; anything else is a row. |
| 138 | let argument = |out: &mut String, index: usize| match arguments | 153 | let argument = |out: &mut String, index: usize| match arguments |
| 139 | .get(index) | 154 | .get(index) |
| 140 | .map(Vec::as_slice) | 155 | .map(Vec::as_slice) |
| 141 | { | 156 | { |
| 142 | Some([single]) if !matches!(single, Math::Object { .. }) => single.write(out), | 157 | Some([single]) |
| 158 | if !matches!(single, Math::Object { .. } | Math::Function(_)) => | ||
| 159 | { | ||
| 160 | single.write(out) | ||
| 161 | } | ||
| 143 | Some(many) => { | 162 | Some(many) => { |
| 144 | out.push_str("<mml:mrow>"); | 163 | out.push_str("<mml:mrow>"); |
| 145 | for node in many { | 164 | for node in many { |
| ... | @@ -165,8 +184,41 @@ impl Math { | ... | @@ -165,8 +184,41 @@ impl Math { |
| 165 | (25, _) => wrapped(out, "msqrt", &[arguments.len() - 1]), | 184 | (25, _) => wrapped(out, "msqrt", &[arguments.len() - 1]), |
| 166 | (19, 2) => wrapped(out, "munder", &[0, 1]), | 185 | (19, 2) => wrapped(out, "munder", &[0, 1]), |
| 167 | (33, 2) => wrapped(out, "mover", &[0, 1]), | 186 | (33, 2) => wrapped(out, "mover", &[0, 1]), |
| 168 | // OneNote exports parentheses as default fences, whatever the pair. | 187 | // Parentheses are the default fences; other pairs name themselves. |
| 169 | (13, _) => wrapped(out, "mfenced", &[0]), | 188 | (13, _) => { |
| 189 | let (open, close) = (symbols.first(), symbols.get(1)); | ||
| 190 | if open == Some(&'(') && close == Some(&')') { | ||
| 191 | wrapped(out, "mfenced", &[0]); | ||
| 192 | } else { | ||
| 193 | out.push_str("<mml:mfenced"); | ||
| 194 | if let Some(open) = open { | ||
| 195 | out.push_str(&format!(" open=\"{}\"", escaped(*open))); | ||
| 196 | } | ||
| 197 | if let Some(close) = close { | ||
| 198 | out.push_str(&format!(" close=\"{}\"", escaped(*close))); | ||
| 199 | } | ||
| 200 | out.push('>'); | ||
| 201 | argument(out, 0); | ||
| 202 | out.push_str("</mml:mfenced>"); | ||
| 203 | } | ||
| 204 | } | ||
| 205 | (10, 1) | (23, 1) => { | ||
| 206 | let accent = *kind == 10; | ||
| 207 | out.push_str(&format!("<mml:mover accent=\"{accent}\">")); | ||
| 208 | argument(out, 0); | ||
| 209 | if accent { | ||
| 210 | tag( | ||
| 211 | out, | ||
| 212 | "mo", | ||
| 213 | &spacing(symbols.first().copied().unwrap_or('^')).to_string(), | ||
| 214 | ); | ||
| 215 | } else { | ||
| 216 | out.push_str("<mml:mo stretchy=\"true\">"); | ||
| 217 | out.push(symbols.first().copied().unwrap_or('¯')); | ||
| 218 | out.push_str("</mml:mo>"); | ||
| 219 | } | ||
| 220 | out.push_str("</mml:mover>"); | ||
| 221 | } | ||
| 170 | // Lower limit, upper limit, body; integrals take their limits as scripts, | 222 | // Lower limit, upper limit, body; integrals take their limits as scripts, |
| 171 | // other operators above and below. | 223 | // other operators above and below. |
| 172 | (21, 3) => { | 224 | (21, 3) => { |
| ... | @@ -204,6 +256,25 @@ impl Math { | ... | @@ -204,6 +256,25 @@ impl Math { |
| 204 | } | 256 | } |
| 205 | } | 257 | } |
| 206 | 258 | ||
| 259 | fn escaped(c: char) -> String { | ||
| 260 | match c { | ||
| 261 | '<' => "&lt;".into(), | ||
| 262 | '&' => "&amp;".into(), | ||
| 263 | '"' => "&quot;".into(), | ||
| 264 | c => c.to_string(), | ||
| 265 | } | ||
| 266 | } | ||
| 267 | |||
| 268 | /// The spacing character OneNote exports for a combining accent. | ||
| 269 | fn spacing(c: char) -> char { | ||
| 270 | match c { | ||
| 271 | '\u{302}' => '^', | ||
| 272 | '\u{303}' => '~', | ||
| 273 | '\u{308}' => '¨', | ||
| 274 | c => c, | ||
| 275 | } | ||
| 276 | } | ||
| 277 | |||
| 207 | fn tag(out: &mut String, element: &str, content: &str) { | 278 | fn tag(out: &mut String, element: &str, content: &str) { |
| 208 | out.push_str(&format!("<mml:{element}>")); | 279 | out.push_str(&format!("<mml:{element}>")); |
| 209 | for c in content.chars() { | 280 | for c in content.chars() { |
| ... | @@ -264,6 +335,7 @@ fn write_sequence(nodes: &[Math], runs: &mut Vec<(String, Option<MathObject>)>) | ... | @@ -264,6 +335,7 @@ fn write_sequence(nodes: &[Math], runs: &mut Vec<(String, Option<MathObject>)>) |
| 264 | for node in nodes { | 335 | for node in nodes { |
| 265 | match node { | 336 | match node { |
| 266 | Math::Identifier(c) => leaf.push(italic(*c)), | 337 | Math::Identifier(c) => leaf.push(italic(*c)), |
| 338 | Math::Function(name) => leaf.push_str(name), | ||
| 267 | Math::Number(n) => leaf.push_str(n), | 339 | Math::Number(n) => leaf.push_str(n), |
| 268 | Math::Operator(c) => leaf.push(*c), | 340 | Math::Operator(c) => leaf.push(*c), |
| 269 | Math::Object { | 341 | Math::Object { |
crates/onestore/tests/page_math.rs+10| ... | @@ -12,6 +12,12 @@ const NATIVE_READ: &str = include_str!("../../../corpus/m6/native-math-01/read/p | ... | @@ -12,6 +12,12 @@ const NATIVE_READ: &str = include_str!("../../../corpus/m6/native-math-01/read/p |
| 12 | /// subscripts, a sub-superscript, parentheses, an integral and a sum with limits. | 12 | /// subscripts, a sub-superscript, parentheses, an integral and a sum with limits. |
| 13 | const EDITOR: &[u8] = include_bytes!("../../../corpus/math-edit/native-editor/notebook/links.one"); | 13 | const EDITOR: &[u8] = include_bytes!("../../../corpus/math-edit/native-editor/notebook/links.one"); |
| 14 | const EDITOR_READ: &str = include_str!("../../../corpus/math-edit/native-editor/read/page-000.xml"); | 14 | const EDITOR_READ: &str = include_str!("../../../corpus/math-edit/native-editor/read/page-000.xml"); |
| 15 | /// A second editor session: square and n-th roots, a plain fraction, a limit, a product, | ||
| 16 | /// square brackets, an overbar and a hat. | ||
| 17 | const EDITOR_2: &[u8] = | ||
| 18 | include_bytes!("../../../corpus/math-edit/native-editor-2/notebook/links.one"); | ||
| 19 | const EDITOR_2_READ: &str = | ||
| 20 | include_str!("../../../corpus/math-edit/native-editor-2/read/page-000.xml"); | ||
| 15 | 21 | ||
| 16 | fn native_mathml(read: &str) -> Vec<String> { | 22 | fn native_mathml(read: &str) -> Vec<String> { |
| 17 | read.split("<mml:math") | 23 | read.split("<mml:math") |
| ... | @@ -91,6 +97,10 @@ fn equation_editor_expressions_render_to_the_mathml_onenote_exports() { | ... | @@ -91,6 +97,10 @@ fn equation_editor_expressions_render_to_the_mathml_onenote_exports() { |
| 91 | rendered_equations(EDITOR, "Read about Rust the Rust site"), | 97 | rendered_equations(EDITOR, "Read about Rust the Rust site"), |
| 92 | native_mathml(EDITOR_READ) | 98 | native_mathml(EDITOR_READ) |
| 93 | ); | 99 | ); |
| 100 | assert_eq!( | ||
| 101 | rendered_equations(EDITOR_2, "Read about Rust the Rust site"), | ||
| 102 | native_mathml(EDITOR_2_READ) | ||
| 103 | ); | ||
| 94 | } | 104 | } |
| 95 | 105 | ||
| 96 | fn object(kind: u32, symbols: &str, arguments: Vec<Vec<Math>>) -> Math { | 106 | fn object(kind: u32, symbols: &str, arguments: Vec<Vec<Math>>) -> Math { |
tools/native_handwriting.py created+67| ... | @@ -0,0 +1,67 @@ | ||
| 1 | """Draw ink over an outline's text and in empty space with the mouse in OneNote 2010 on a | ||
| 2 | Rust-written page, then capture the COM read: the oracle for handwriting held by paragraphs. | ||
| 3 | |||
| 4 | Usage: native_handwriting.py NOTEBOOK_DIR OUTPUT_DIR | ||
| 5 | """ | ||
| 6 | import base64 | ||
| 7 | import json | ||
| 8 | import sys | ||
| 9 | from pathlib import Path | ||
| 10 | import xml.etree.ElementTree as ET | ||
| 11 | |||
| 12 | sys.path.insert(0, str(Path(__file__).resolve().parent)) | ||
| 13 | import native_runner as runner | ||
| 14 | |||
| 15 | windows = runner.windows | ||
| 16 | |||
| 17 | |||
| 18 | def interaction(name, output): | ||
| 19 | page = ET.parse(sorted((output / 'read').glob('page-*.xml'))[0]).getroot().attrib['ID'] | ||
| 20 | script = '''ComObject("OneNote.Application").NavigateTo("PAGE_ID", "", false) | ||
| 21 | WinWait("ahk_exe ONENOTE.EXE", , 30) | ||
| 22 | WinActivate("ahk_exe ONENOTE.EXE") | ||
| 23 | WinWaitActive("ahk_exe ONENOTE.EXE", , 30) | ||
| 24 | WinMaximize("ahk_exe ONENOTE.EXE") | ||
| 25 | Sleep(3000) | ||
| 26 | SendMode("Event") | ||
| 27 | CoordMode("Mouse", "Screen") | ||
| 28 | Click(258,36) | ||
| 29 | Sleep(600) | ||
| 30 | Click(205,69) | ||
| 31 | Sleep(600) | ||
| 32 | ; handwriting inside the outline, below its text line | ||
| 33 | MouseMove(140,160) | ||
| 34 | Click("Down") | ||
| 35 | MouseMove(160,175,25) | ||
| 36 | MouseMove(180,160,25) | ||
| 37 | MouseMove(200,175,25) | ||
| 38 | MouseMove(220,160,25) | ||
| 39 | Click("Up") | ||
| 40 | Sleep(300) | ||
| 41 | MouseClickDrag("Left",240,158,300,178,25) | ||
| 42 | Sleep(300) | ||
| 43 | ; a drawing in empty page space | ||
| 44 | MouseMove(450,320) | ||
| 45 | Click("Down") | ||
| 46 | MouseMove(500,360,25) | ||
| 47 | MouseMove(450,400,25) | ||
| 48 | MouseMove(400,360,25) | ||
| 49 | MouseMove(450,320,25) | ||
| 50 | Click("Up") | ||
| 51 | Sleep(1000) | ||
| 52 | Click(21,74) | ||
| 53 | Sleep(2000) | ||
| 54 | '''.replace('PAGE_ID', page) | ||
| 55 | (output / 'handwriting.ahk').write_text(script) | ||
| 56 | result = windows.do_exec(script, target=name, timeout_ms=120000, shot_delay_ms=1500) | ||
| 57 | screenshot = result.pop('png_b64', None) | ||
| 58 | if screenshot: | ||
| 59 | (output / 'handwriting.png').write_bytes(base64.b64decode(screenshot)) | ||
| 60 | (output / 'handwriting.json').write_text(json.dumps(result, indent=2)) | ||
| 61 | if result.get('error') or result.get('exit') != 0: | ||
| 62 | raise RuntimeError(str(result)) | ||
| 63 | |||
| 64 | |||
| 65 | if __name__ == '__main__': | ||
| 66 | notebook, output = Path(sys.argv[1]), Path(sys.argv[2]) | ||
| 67 | runner.capture(notebook, output, expected_pages=1, collect_notebook=True, interaction=interaction) | ||
tools/native_math.py+3| ... | @@ -5,6 +5,7 @@ Usage: native_math.py NOTEBOOK_DIR OUTPUT_DIR | ... | @@ -5,6 +5,7 @@ Usage: native_math.py NOTEBOOK_DIR OUTPUT_DIR |
| 5 | """ | 5 | """ |
| 6 | import base64 | 6 | import base64 |
| 7 | import json | 7 | import json |
| 8 | import os | ||
| 8 | import sys | 9 | import sys |
| 9 | from pathlib import Path | 10 | from pathlib import Path |
| 10 | import xml.etree.ElementTree as ET | 11 | import xml.etree.ElementTree as ET |
| ... | @@ -23,6 +24,8 @@ EQUATIONS = [ | ... | @@ -23,6 +24,8 @@ EQUATIONS = [ |
| 23 | '\\int_0^1 x dx', | 24 | '\\int_0^1 x dx', |
| 24 | '\\sum_(i=1)^n i', | 25 | '\\sum_(i=1)^n i', |
| 25 | ] | 26 | ] |
| 27 | if os.environ.get('ONESTORE_MATH_EQUATIONS'): | ||
| 28 | EQUATIONS = os.environ['ONESTORE_MATH_EQUATIONS'].split('|') | ||
| 26 | 29 | ||
| 27 | 30 | ||
| 28 | def interaction(name, output): | 31 | def interaction(name, output): |
tools/test_math_edit.py+1| ... | @@ -22,6 +22,7 @@ def exported_mathml(path): | ... | @@ -22,6 +22,7 @@ def exported_mathml(path): |
| 22 | class MathEditTest(unittest.TestCase): | 22 | class MathEditTest(unittest.TestCase): |
| 23 | def test_equation_editor_expressions_export_as_mathml(self): | 23 | def test_equation_editor_expressions_export_as_mathml(self): |
| 24 | self.assertEqual(len(exported_mathml(FIXTURE / 'native-editor/read/page-000.xml')), 5) | 24 | self.assertEqual(len(exported_mathml(FIXTURE / 'native-editor/read/page-000.xml')), 5) |
| 25 | self.assertEqual(len(exported_mathml(FIXTURE / 'native-editor-2/read/page-000.xml')), 9) | ||
| 25 | 26 | ||
| 26 | def test_rust_written_equations_export_the_expected_mathml(self): | 27 | def test_rust_written_equations_export_the_expected_mathml(self): |
| 27 | with TemporaryDirectory() as temporary: | 28 | with TemporaryDirectory() as temporary: |