| 1 | #!/usr/bin/env python3 |
| 2 | """Generate schema-validated inputs for native document feature controls.""" |
| 3 | import argparse |
| 4 | import json |
| 5 | import os |
| 6 | from pathlib import Path |
| 7 | import shutil |
| 8 | import wave |
| 9 | |
| 10 | from lxml import etree as ET |
| 11 | from PIL import Image |
| 12 | |
| 13 | ROOT = Path(__file__).resolve().parent.parent |
| 14 | NS = 'http://schemas.microsoft.com/office/onenote/2010/onenote' |
| 15 | |
| 16 | |
| 17 | def generate(destination): |
| 18 | destination.mkdir(parents=True, exist_ok=False) |
| 19 | notebook = destination / 'notebook' |
| 20 | shutil.copytree(ROOT / 'corpus/writer/create-notebook-01/notebook', notebook) |
| 21 | assets = notebook / 'fixture-data'; assets.mkdir() |
| 22 | image = Image.new('RGB', (96, 64)) |
| 23 | image.putdata([(x * 2, y * 3, (x + y) % 256) for y in range(64) for x in range(96)]) |
| 24 | for extension in ('png', 'jpg', 'bmp', 'tiff', 'gif'): |
| 25 | image.save(assets / ('pattern.' + extension)) |
| 26 | (assets / 'attachment.txt').write_bytes(b'Fictitious attachment\r\n\x00\xff\r\n') |
| 27 | with wave.open(str(assets / 'silence.wav'), 'wb') as sound: |
| 28 | sound.setparams((1, 2, 8000, 8000, 'NONE', 'not compressed')) |
| 29 | sound.writeframes(b'\0' * 16000) |
| 30 | fixture = {'sections': ['Features.one', 'Group A/Duplicate.one', 'Group B/Nested/Duplicate.one', 'Empty.one'], 'pages': []} |
| 31 | xsd = os.environ.get('ONENOTE_2010_XSD') |
| 32 | if not xsd: |
| 33 | raise SystemExit('Set ONENOTE_2010_XSD to the OneNote 2010 COM schema (OneNote2010.xsd); it is not distributed with this repository.') |
| 34 | schema = ET.XMLSchema(ET.parse(xsd)) |
| 35 | |
| 36 | def page(title, content, section='Features.one', attributes='', definitions=''): |
| 37 | xml = f'<one:Page xmlns:one="{NS}" {attributes}>{definitions}<one:Title><one:OE><one:T>{title}</one:T></one:OE></one:Title>{content}</one:Page>' |
| 38 | schema.assertValid(ET.fromstring(xml.encode())) |
| 39 | fixture['pages'].append({'section': section, 'xml': xml}) |
| 40 | |
| 41 | def outline(body, x=72, y=108): |
| 42 | return f'<one:Outline><one:Position x="{x}" y="{y}"/><one:Size width="480" height="400"/><one:OEChildren>{body}</one:OEChildren></one:Outline>' |
| 43 | |
| 44 | for extension in ('png', 'jpg', 'bmp', 'tiff', 'gif'): |
| 45 | body = f'<one:Image format="auto" alt="Pattern {extension}" hyperlink="https://example.invalid/image/{extension}"><one:Position x="72" y="108"/><one:Size width="144" height="48"/><one:File path="__ASSETS__\\pattern.{extension}"/></one:Image>' |
| 46 | page('Image ' + extension, body) |
| 47 | for background, printout in [('false', 'false'), ('true', 'false'), ('false', 'true'), ('true', 'true')]: |
| 48 | page(f'Image role background={background}, printout={printout}', f'<one:Image format="auto" backgroundImage="{background}" isPrintOut="{printout}" originalPageNumber="2" alt="Role pattern"><one:Position x="36" y="36"/><one:Size width="288" height="192"/><one:File path="__ASSETS__\\pattern.png"/></one:Image>' + outline('<one:OE><one:T>Foreground role control</one:T></one:OE>')) |
| 49 | page('Files and recording', outline('<one:OE><one:InsertedFile pathSource="__ASSETS__\\attachment.txt" preferredName="Fictitious attachment.txt"/></one:OE><one:OE><one:MediaFile pathSource="__ASSETS__\\silence.wav" preferredName="silence.wav"><one:MediaReference mediaID="{9A418E57-7F64-40C5-87D0-8A8F7C1E5422}"/></one:MediaFile></one:OE><one:OE><one:MediaIndex timeIndex="500"><one:MediaReference mediaID="{9A418E57-7F64-40C5-87D0-8A8F7C1E5422}"/></one:MediaIndex><one:T>Recording annotation</one:T></one:OE>')) |
| 50 | page('Paragraph controls', outline('<one:OE quickStyleIndex="0"><one:T>Inherited strike and spacing</one:T></one:OE><one:OE alignment="center" spaceBefore="12" spaceAfter="6"><one:T>Centered paragraph</one:T></one:OE><one:OE RTL="true" alignment="right"><one:T>مرحبا بالعالم</one:T></one:OE><one:OE collapsed="true"><one:T>Collapsed parent</one:T><one:OEChildren indent="2"><one:OE><one:T>Hidden child remains stored</one:T><one:OEChildren><one:OE><one:T>Grandchild</one:T></one:OE></one:OEChildren></one:OE></one:OEChildren></one:OE><one:OE><one:T><![CDATA[A<br><br>B&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;C]]></one:T></one:OE>'), definitions='<one:QuickStyleDef index="0" name="Fixture style" font="Calibri" fontSize="14" strikethrough="true" spaceBefore="0.25" spaceAfter="0.125"/>') |
| 51 | body = '' |
| 52 | for sequence in range(5): |
| 53 | for index in range(2): |
| 54 | restart = ' restartNumberingAt="3"' if index == 0 else '' |
| 55 | body += f'<one:OE><one:List><one:Number numberSequence="{sequence}" numberFormat="##"{restart} font="Calibri"/></one:List><one:T>Sequence {sequence}, item {index}</one:T></one:OE>' |
| 56 | page('Numbering controls', outline(body)) |
| 57 | page('Task controls', outline('<one:OE><one:OutlookTask guidTask="{45AF40E3-1D54-463C-90D3-C454F2F71A2E}" completed="false" disabled="true" creationDate="2020-01-02T03:04:05Z" startDate="2020-01-02T00:00:00Z" dueDate="2020-01-03T00:00:00Z"/><one:T>Disabled standalone task</one:T></one:OE>')) |
| 58 | for section in ('Group A/Duplicate.one', 'Group B/Nested/Duplicate.one'): |
| 59 | for level in (1, 2, 3): |
| 60 | page('Repeated title', outline(f'<one:OE><one:T>Group path {section}, level {level}</one:T></one:OE>'), section, f'pageLevel="{level}"') |
| 61 | (notebook / 'fixture.json').write_text(json.dumps(fixture, ensure_ascii=False, indent=2)) |
| 62 | print(f'Generated {len(fixture["pages"])} schema-validated native pages; expected total {len(fixture["pages"]) + 1}.') |
| 63 | |
| 64 | |
| 65 | if __name__ == '__main__': |
| 66 | parser = argparse.ArgumentParser(description=__doc__) |
| 67 | parser.add_argument('destination', type=Path) |
| 68 | generate(parser.parse_args().destination) |