1#!/usr/bin/env python3
2"""Generate schema-validated inputs for native document feature controls."""
3import argparse
4import json
5import os
6from pathlib import Path
7import shutil
8import wave
9
10from lxml import etree as ET
11from PIL import Image
12
13ROOT = Path(__file__).resolve().parent.parent
14NS = 'http://schemas.microsoft.com/office/onenote/2010/onenote'
15
16
17def 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
65if __name__ == '__main__':
66 parser = argparse.ArgumentParser(description=__doc__)
67 parser.add_argument('destination', type=Path)
68 generate(parser.parse_args().destination)