From 18dc4eec942c04ff4a39ff6ce7270bd7a73e087f Mon Sep 17 00:00:00 2001 From: Oliver Terbu Date: Sun, 5 Jul 2026 17:56:47 +0200 Subject: [PATCH 01/13] chore(draft): setup github action for generating working draft .html/ .docx --- .github/workflows/build.yml | 91 +++++++++++ .gitignore | 5 + Makefile | 45 +++++ README.md | 7 +- ...tal-credentials-harmonized-presentation.md | 103 ++++++++++++ template/Word_template_for_ISO_standards.dotx | Bin 0 -> 89499 bytes tools/iso-styles.lua | 82 ++++++++++ tools/make-iso-reference.py | 154 ++++++++++++++++++ tools/mmark-to-pandoc.py | 94 +++++++++++ 9 files changed, 578 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/build.yml create mode 100644 Makefile create mode 100644 draft/digital-credentials-harmonized-presentation.md create mode 100644 template/Word_template_for_ISO_standards.dotx create mode 100644 tools/iso-styles.lua create mode 100644 tools/make-iso-reference.py create mode 100644 tools/mmark-to-pandoc.py diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..b7118fe --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,91 @@ +name: Build DCHP specification + +on: + push: + paths: + - 'draft/**' + - 'tools/**' + - 'template/**' + - 'Makefile' + - '.github/workflows/build.yml' + pull_request: + paths: + - 'draft/**' + - 'tools/**' + - 'template/**' + - 'Makefile' + - '.github/workflows/build.yml' + workflow_dispatch: + +permissions: + contents: read + +env: + DOC: digital-credentials-harmonized-presentation + +jobs: + build: + name: Build HTML Working Group Draft and ISO Word document + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + # HTML Working Group Draft (markdown2rfc / mmark), same tooling as other + # OpenID DCP specifications; published to GitHub Pages below. + - name: Render HTML Working Group Draft + run: make html + + # ISO-styled Word document (pandoc), for sharing the draft with ISO. + # Delivered as a workflow artifact only; not published to the site. + - name: Install pandoc + run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends pandoc + + - name: Render ISO Word document + run: make docx + + - name: Assemble HTML for GitHub Pages + run: | + mkdir -p _site + cp build/*.html _site/ + # Redirect the bare Pages URL to the Working Group Draft (avoids a root 404). + printf '\n\n' \ + "${{ env.DOC }}" > _site/index.html + + - name: Upload site artifact + uses: actions/upload-artifact@v4 + with: + name: site + path: _site + + - name: Upload ISO Word document artifact + uses: actions/upload-artifact@v4 + with: + name: iso-word-document + path: build/${{ env.DOC }}.docx + + publish-to-pages: + name: Publish Working Group Draft to GitHub Pages + if: github.ref == 'refs/heads/main' + needs: build + runs-on: ubuntu-latest + permissions: + pages: write + id-token: write + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - name: Download site artifact + uses: actions/download-artifact@v4 + with: + name: site + path: _site + - name: Upload Pages artifact + uses: actions/upload-pages-artifact@v3 + with: + path: _site + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.gitignore b/.gitignore index 15d664c..723cc0a 100644 --- a/.gitignore +++ b/.gitignore @@ -6,11 +6,16 @@ *.redxml *.upload /*-[0-9][0-9].xml +/draft/*.xml /.refcache /versioned/ archive.json report.xml +# Build outputs — HTML draft, ISO Word document and all intermediates. +# Source scripts live in tools/; the ISO template lives in template/. +/build/ + # Build tooling /.gems/ /.targets.mk diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..959278e --- /dev/null +++ b/Makefile @@ -0,0 +1,45 @@ +# Build the DCHP specification. +# +# make html - render the HTML Working Group Draft with markdown2rfc (Docker) +# make docx - render the ISO-styled Word document with pandoc +# make all - build both +# make clean - remove the build/ directory +# +# Source scripts live in tools/; everything generated goes to build/ (which is +# git-ignored). `make html` needs Docker; `make docx` needs pandoc and python3. + +SRCDIR := draft +DOC := digital-credentials-harmonized-presentation +SRC := $(SRCDIR)/$(DOC).md +TEMPLATE := template/Word_template_for_ISO_standards.dotx + +TOOLS := tools +BUILD := build + +.PHONY: all html docx clean + +all: html docx + +## HTML Working Group Draft (markdown2rfc / mmark) -> build/ +html: $(SRC) + docker run --rm -v "$(CURDIR)/$(SRCDIR):/data" danielfett/markdown2rfc $(DOC).md + mkdir -p $(BUILD) + cp $(SRCDIR)/$(DOC)*.html $(BUILD)/$(DOC)-wg-draft.html + rm -f $(SRCDIR)/$(DOC)*.html $(SRCDIR)/$(DOC)*.xml $(SRCDIR)/$(DOC)*.txt + @echo "HTML Working Group Draft -> $(BUILD)/$(DOC)-wg-draft.html" + +## ISO-styled Word document (pandoc) -> build/ +docx: $(SRC) $(TEMPLATE) $(TOOLS)/make-iso-reference.py $(TOOLS)/mmark-to-pandoc.py $(TOOLS)/iso-styles.lua + mkdir -p $(BUILD) + python3 $(TOOLS)/make-iso-reference.py "$(TEMPLATE)" "$(BUILD)/iso-reference.docx" + python3 $(TOOLS)/mmark-to-pandoc.py < $(SRC) > $(BUILD)/$(DOC).pandoc.md + pandoc $(BUILD)/$(DOC).pandoc.md \ + --reference-doc=$(BUILD)/iso-reference.docx \ + --lua-filter=$(TOOLS)/iso-styles.lua \ + -o $(BUILD)/$(DOC).docx + rm -f $(BUILD)/iso-reference.docx $(BUILD)/$(DOC).pandoc.md + @echo "ISO Word document -> $(BUILD)/$(DOC).docx" + +clean: + rm -rf $(BUILD) + rm -f $(SRCDIR)/$(DOC)*.html $(SRCDIR)/$(DOC)*.xml $(SRCDIR)/$(DOC)*.txt diff --git a/README.md b/README.md index 589f1c3..a1da5be 100644 --- a/README.md +++ b/README.md @@ -24,9 +24,10 @@ for more information. ## Specifications -The working group is at an early stage and no drafts have been published yet. -Links to the Editor's Copy and Working Group Drafts will be added here as they -become available. +The current Working Group Draft is built automatically from [draft/](draft/) on +every change to the `main` branch: + +- [Digital Credentials Harmonized Presentation — Working Group Draft](https://openid.github.io/dchp/digital-credentials-harmonized-presentation-wg-draft.html) ## Contributing diff --git a/draft/digital-credentials-harmonized-presentation.md b/draft/digital-credentials-harmonized-presentation.md new file mode 100644 index 0000000..dd740ff --- /dev/null +++ b/draft/digital-credentials-harmonized-presentation.md @@ -0,0 +1,103 @@ +%%% +title = "Digital Credentials Harmonized Presentation - Working Group Draft" +abbrev = "dchp" +ipr = "none" +workgroup = "Digital Credentials Harmonized Presentation" +keyword = ["digital credentials", "mdoc", "sd-jwt vc", "presentation", "iso"] + +[seriesInfo] +name = "Internet-Draft" +value = "digital-credentials-harmonized-presentation" +status = "standard" + +[[author]] +initials = "TBD" +surname = "Editor" +fullname = "TBD Editor" +organization = "OpenID Foundation" + [author.address] + email = "openid-specs-dchp@lists.openid.net" + +%%% + +.# Abstract + +This document specifies a harmonized protocol for the presentation of digital +credentials, bringing together the credential presentation approaches of +ISO/IEC 18013-5 and OpenID for Verifiable Presentations across multiple +credential formats, including ISO mdoc and IETF SD-JWT VC. + +{mainmatter} + +# Foreword + +This specification has been jointly developed by members of ISO/IEC JTC 1/SC 17 +WG 10 and members of the OpenID Foundation Digital Credentials Protocols (DCP) +Working Group, under the OpenID Foundation's Digital Credentials Harmonized +Presentation (DCHP) Working Group. + +This is a Working Group Draft. It is a work in progress and is subject to change +at any time. + +# Introduction + +ISO/IEC 18013-5 (Device Request / Device Response) and OpenID for Verifiable +Presentations (Authorization Request / Authorization Response) take different +approaches to credential presentation. This document defines a harmonized +digital credentials request protocol that brings these together, supporting both +in-person and online presentation and the coexistence of multiple credential +formats, including ISO mdoc and IETF SD-JWT VC. + +The objective is to enable interoperability among the parties involved in the +presentation of digital credentials while allowing existing deployments to +continue to operate. + +# Scope + +To be completed. + +# Normative references + +The following documents are referred to in the text in such a way that some or +all of their content constitutes requirements of this document. + +To be completed. + +# Terms and definitions + +For the purposes of this document, the following terms and definitions apply. + +ISO and IEC maintain terminology databases for use in standardization at the +following addresses: + +* ISO Online browsing platform: available at +* IEC Electropedia: available at + +To be completed. + +# Symbols and abbreviated terms + +To be completed. + +# Conventions + +In this document, the following verbal forms are used: + +* "shall" indicates a requirement; +* "should" indicates a recommendation; +* "may" indicates a permission; +* "can" indicates a possibility or a capability. + +These verbal forms are used in accordance with ISO/IEC Directives, Part 2, +Clause 7 (see ). + +# Requirements + +To be completed. + +{backmatter} + +# Bibliography + +[1] ISO/IEC Directives, Part 2, *Principles and rules for the structure and +drafting of ISO and IEC documents* diff --git a/template/Word_template_for_ISO_standards.dotx b/template/Word_template_for_ISO_standards.dotx new file mode 100644 index 0000000000000000000000000000000000000000..06754622bc482b3fcd8c8a964d0b459377c8589e GIT binary patch literal 89499 zcmeF0W3MPcw4S$Z+qTcPZQHhO+qTcPZQHhOn?3h7P203df57d0m`pNA)@0sy%{(J7 z1q^}$00sa7002M;@P$0e2@V7RAPWZofD8Zuq$Ol$>uh4{tf%Z@Z{nmw=Wb(7PzVA< zkq_{1(El(0m3Lq&ZPIp=0YT&y^aCEKHAI-)CaAD3TLgcSj9)2VOA=?@`W;AO^kvtL zWJ554GXY0bh7E{hX3NP;T>j|GvC`NTdIq{k%29Pn9IS&)`^-ebcguU4FMbNVgd<5* zNWyh*{P^Ix@_l+HaF#*qGqFmr4xjS*84Bewq(0xFm{GRgzt&cQw$iVWbnbEeDsV*< ze2x&l8cbt~PiSz3$yl%Djf1m1m~6ciW3^Cnpem)%6fo?(`oxW(-2zJi_|LPZfusS6 zwn}e4Trl3U8^O1QzYi#=sVk!|X_a3P;41=KMLRG;#dRN>omlK?ljUpHMq`bANA_O4 z4l{ZWV~B#Y5l*SkJ^UohpIVj1nWa6R8eq~WfCE6zvsWjEI*%SCwkn(_#Rb>3?V_?Px%r4 zYGC2BPdQ~t9uN@;+X4`^1UHB#_%{GHuC`)1{+>jV1N1-t&~r4gcA}^IkNW@n#(!nE_@A*?CCW$v zGa`iE2L2LGb#rY}5iM-%3ZKase*+3JeFkK~CziMT?odS1Ej5SXF?*bjGYhR`b>l8m zkFvO{^*2xm)GzM9)Ku=Zd#G;(N(#=*o#Cq4?}21}JQ#it5lh%gej_AKprlcqi45Qx zF7RkchQMDv$XphYj*qSx5PS>b9#+uUG5nbF^B7cAmlN1dqn4c@yrHz%Z3;YguX0Vw z#&QRAp}U4o6Af+Qn~W`%g&K}32HbxzrXedrh=>B?{n5;^eR^#&*BFaOu*CP+IVEN! zZ{j|UEQ!F+`hyRfS^JPZ{mfzWL>JNeTXo9(SRI@cr_l@if0K(=fAyX1e@F!h000L7 z0?^IQ(U|_fYsT2l$i?Pg=KYV9`#Pt?K&$TB>;8Fi!-=Z?u?hg3#RoI#NmNJ$qWzls%LA35mX%|cycFk zngx}Ny+&k7@Ci7xpVkO5ISr*f;W>W(+2pFviFD2Rf<$05jFliwxok9Q z0B(iYYLO|g*$rNiKVi=~&@5!=$M>!RO>+N^2}$jnXv^As_P&zbOzEv&s@tX>=xgK zL9z4v*k*6G5I$|TuwAXf3O@D^s`%+hhYjSn_Ev58G{N=Am^@M_dS@5h26Qf>8SdjB zHDfr^qZtAw(?z^{7j210MH|;Y#MXLZ&*~47G82YXOAGp^N62dcf8i{3CbS1MJdQ2WY(#9LwA};zhsiFb{cR%!gG3t2y4kQR%e+!>- zM-0f8C2HfrL<>^19;&Y1kJUM!NG}%aqWJ!a>;Ft|g*L0tkwO3fM!G-%i2p|Xr(XY8 zrC#Z1*&T|e`s(Tb3JHAI*+$bwperPrU1@Bu+30iHH04~wkVT0^&}gKTs7og_T=`DK zrKkBozZe93W4}|rlX}S}EPV@(Cz_O=6VOIfak^Y=^(g&$p!f5^a6if&kB9y(%txIB z9q<-w+v@G4@bi3X#o&HCM;67mJjCDl8-hBb~o=2ul$a>>%x~N?C5@hYAZJY+5b+;emyQs52R2B_19Y0sXtPg{PwXq^fQS4UMA}PM*hwv=3iYkt-me3$QvJ8lCM<(2}3{W)r zBPqpcb}cHQSFF4plUOfa-h%xX)Cr3Qs|_L@{*RX+oH+>Jv5p1*ahicWSTHcwJ>9GO z69mhos${6+?<5?KKs94Xz`m_afhHD2nn!4sA|iT7!P9KghuPuB*oTKU-#f;tzHWv!nS2=%#+rnXkaadCSup+nq)<1c>16xn=zen2e0qdB zoK!>_MXcsv(eVuTKMVVq+f;v%tw7K3BJ`W18SQg3D1mP5sgXJo9s=;yeIixQL~Z@J z9Lr-R)zd_qs` zPu>i(p3ml@(4zF@bQHC&fST(;^lqw8%_QP@VgO8xP&cF%I< z>jgHN)Yla)HD#%iwfa3#X_?HiG#cr;3N^KQY4Q+h!Y0cr&*8u;LaGD>8Ys|UL&}nh z3I%i_SP=l+h{9k1OlZR4+VeDuAzds%)H3tA^em^)uTz!gw}vHw&ge1=nRIH1Eh|vF znEo)u{l)z`Ik&EIZi!3f^JbUrymkd6u4chr<}9Wbiwr=HdSo~3GAPG-J6uE{OcnJr zVv(LRHw?zRtQMmX(gTTo)1)e-eB4H1Z6WVjNVoGis!svyjtrp9>fvsGAg0_ZK!$u5 zPam~!d)}<={e6&o01Sm{RI4!%hef>B_8~-MN(beUsVr2fv}sB>)Rax@2Z)dfRH;}y zw#LStWUGct)~RYZIgmD6voruf5(o z*o+rhecPgI-uU~nEHfVJXD|3-i}tC?F0*;lq_xqgnRhXbgN@o)y*}-F15&#Pc>*Z0 zyL`2ZtQac$H_idgkmh|04ia=k>zW~`8aqaH+0r!@6rnxAhOJ=IvL{ow1g~A;QhR|mky>T$*nq^t4TDh)v)K4GJ0@9%)6oBD zA#uO%6WNI%2XW|J={Gh3G48_j zSx*5d0D*;w(Wtqc@n4tsoKcX4k37jDeeB129fDrCgA4#KdxC zzr0}+&n;Pi2=jt3aM|-JHxL<=;VBe;S!#K#Y!+AKP=QrY>_ShE6RWGld19yK-1J!(wgmh#o!H*fCD zdjuJ7r=UjK_aN@asIPeRn_f8gkSe{)krkDh(0i{AkNAvR&T#~3w-n`I1ilbZTM2Sj z>hHX{@=<|!nuHgI%FKZc)dFnPxB1?*Sc(I9-k@^o?8ubt5Y6jC#XJjE$F>I=%dU~V z#p(oZW8mIv!y4GPfT1f7*h)0c7C&j%MniL`ih|5Drc1_Ga0d1xt~Cu%643W9b1%a& zt3=l&B%%prjp?u&x|@-N^eV$4mOI~dz5TY8=jZ`;|28bvJ%qMu^rYgRdoqpNNMDbyO~jsqk0yBmw_Xg}s%SF*k4r>0?g8fH3Pn4-568zg&p z=yC1IAXBtRc0ci;4M3pETX60Xz%l?~e7&;UONC|DzPOLrrw4p+vIp)~#4bm=a)5Dp zAr4X~Q!)<-_|3F@7jZ2!C+1!^8s&BDmZo!{^%s-(-ZCcL-)ZiNAL$*&e0t3+OwwW zBklzDgMTHX_>K&^XFEWVh^^8Co+lvQ**KE{Vw`bq_6Fvot& zCxnYuuwHZ^k#3pfVi+XZ-@fYoaDkkF>rg~`MUgGw0x`$czmYYWFbY95WR}d=V1q*l zcm=$2gWy7pCFS5}2qJ%7IADtUDOtUa`zEGgR?P*?g&Dn%W_>fc2iR_O*y0XAI}+?v z?{7ip?txZY8KyS7U|w@l`zmfylj-~Z9{aWIz&6&7XnhkoptBu+B0{#7AVs-r_~4p+;7igr_cRD$%dTwPeu5rFt}NSv7h4m7Hfv z`~WX_%^XpBjC#AuF|!B$wMfYeWv%P}(}u)X z^O=AC%+7biU;q!aqP*u6Ccmzdb6f6 zy=r21ElV~k3uAo?TjP8+Q-gvsOgv;$k z#pB_U>ywSEale39 z`=C<=8(k!>c!KDR5EyH}up~?>kFG$#e|PUAgKvmcs4m42?5`?o(Kdye!{oxBa%F+g zfG8*(y0bnbl-17VM5cZB!hX_p@~~q4RcH+o>x>dO8pF2j^dMKN7B*fWUfQWh4c6$9 z3v-9h`oGh{_7>1b_y~l~?7i2GfWpy$uOZ&McS5l?dx%@2W9LuPvW~E~ul0KinX(u% zI<}5!?ET{d`fY zpraVf&BMMN2g*~bMO)=2{ZGr`i&fYuCz;#3ei%dR2rWydH;2L1_m6l4ep5oLGX<5Y zVKp3sp7jZn4Hwmt&4ERs+>Sh~j^#x}qY>IxZp5@$YnO`rseA2y8FX=Oe_|n2`8>?^ zV1@_Jk1KdF_Z;|}>eo-9ZInG@6&8Ckjg}S##~uxmO`AEJBSXP|9HTvnM9a;{a3PDH zaEfxli+YsgI!<24AJy-+Y|@2P$sFl18k!v8G@M+Y4GAl^tg})~OUx3F;+r8GQh93* zH3lyKCJ3!O@~_Y)1-TEenDE>@x`X~1ty+!M(2V`hS&WmG%`}w;rt8s3Mk{1fieBBZ zK^ms98eVA@lSP)N2sIa{=5@?iXB=2lm@BDHM6+RFnLFhdnVu7>v0!o{z}ypFBrIh| z=J7@SlfuBnVnM5cV5ZHBWq-85cgYyMC=TEw!7flH66W&L?NHWwmF+@x%%G6QA~>H! zxm{SpkMy zPFt_J9`A{fMB4!p9GLCmLlZnO4gnr@03Z@N1>c@v+$%@dIYzs)VkodZnG!E{GB`%B z1tW@ae@9^?z!SgFui_BH1tY#|TzW*WIUXK98U(8_PG}DLZVd7~xSX%u#JMkROxzpX zGEJ}keGLSAzkgN>0yC*LU7q%yz0jS@_vL0OJNfUy27TqfRSE#>w7PWHhu$`Z?fTlpBi|fCgD-2Gv<8=5j&Ffe@1RPY1{_qvEq_i(#O=b8M&(UQ5P8=b z<2}2^czXJ-xbt4vcvPLd7v>sRRhxGE+*SYC6$A4-EGobgO*`yYy59>wbq9oZ6lV9=K2=q4l3&9_c{uE<1w)w#ElIO#rm0057cfAk zz`19q4;1WaHvM&+7BJAukI%dCVa?Z9*|^SB7#D;31PCF{y->-Ik3)qcSjRzo0fm0o z*ii$9UY1?AZnk<49N^X84>$8LQXdn8{!FumgZ>N*`2xAE3;?aRb$xo+moxK0uCpIP z?ux-df+JY=&hemkkI!525or`Ng+1`_MY6(ow;x*UxQC9pC4v2j^Eleza6C2>KP6sG z75mC%Osd{EcCEBA3)j{hWZ1BzeoEdXwY$_snn`ROA$O}OrJ$f&5owbs!#U_v|(3OfpUekyzta^gT#uR2uQM;lrLLV}QYH|@kXObbT& z6K!p035+=mAb}D;0#yhff-2bScqSDl_`_*G-4**E z@gju&9!-%NVkDH&58u_u)rEv}_~dcP&msl^#u1GS5S`1H@-4p+@2`PC6X*B&oXN3Y z{S0M`Kf5oT%4gCMi+rGP4yDWG(}9Y^fARy;7m|?nUIPCCl0-OzAo<&uI(G0xpAjy(VfnpH=L-8aLS|Z4* zO~MjbXg-M6qkmQ0Hn(V*pL#+#vg?zY7XYHfIf3iXhKTmnVPQbW7bOCk!+r~<&L-Rl zVM-~3w^TWSC0tQ?nIcTNg(W>x4{TYY6_3R%NCYgHOX$Mx3}^_vuBu{wXz6t5JGK1AHy39%vPezhbw%;UJm@8%pxlRQw1>tMZXSY9QVLCtYA zdlo#XJlYXI_mNGiss2f{RMWT!g1$Wr2Ban%=?vK|0oGpOP7i*Md>=ZF_9LF& zJsG}l_khf>3~v52KM#Rahe~!$oKjR~_-@3gR>22| z_DBlIAA^}ONoQhzGY~9Fu==3k6{t}0PL!|U9i^N7a;>{znttDFsIiI~Yve0*(m%LY zMlY?APn%LNOmJuh_>Brp(DBB3fM11XLcG%o+Enn4jGoVUjGp@9ee!@R1BB-KWHeO^ z-((o-(@#X)CIY2UF!$7=-8rWNlE$!e8YGm_91bglGa)J~oH2AbK_BcO@m%4`_v~of zkbA(sj=?Z6TKs^6Fg@@p6#4yxnfRjISfcJDIs9<6#$`Z?Xg(@jnvJT{Ez6JCb7?Vf zV1nKd`R0MSm)cf(MWH(%Yu&2~z{QM0W6np!Gv0eih<#NfO8BJ}WBv*$btFX~Q*7>W zTqIb%G3X>9$`0nZf^&idO7@T~;i1tIFt9=t2&dQ#?ih^WTFxsA2Ks!_bs>Jhg>+=h z=Eo9+FPNoPW7Uqi8JM|Av#t1mouse0czgWBm|#1xqjxf_^FY2p#fCuSt<#&$@B>Si zCDo#Lvpipunc^nPcaIo~BLSNm%atJ9utm0Ki#XN85BE%Z9qDcgZ~Wbz1#&x(;T3Rf zXTj(pU~s~RRQs!Fs`5+DPGR@>-l?7v=2S050e|q-TCu@wuLRlP8iijQO0rBxcRXDE zWSncQGB+SUG7CQsl0~@#pzA9?*-`nHx(H=GYoahNL0*i1H9tciqiIKhTVe#?!GrUx zD3_EU+779~O~YfdqK*xou&Rkvfj;>E@J<^hp0@u5fw~&yuSjjhXU>H<*)A~`8$TH zl1F!6Nb-{}c6W9;X)~sEonGhfkK-;nX|5-~*A`4gTs!0px-hSJ{ed?gV_P%@V`b_w z9j5!%W6*FSji18wP_!5KHOigb%RjT@Dbt+mMn{U3Cv6Kd*}Nzw_liK~#pK*_(E}RP zS~dQW8s%=B8M=R8VDqHrT%4?8|Z2w~Ds%V{kl zO%3H$SG-aXBmpuJQ7T|f=q(m zJSSADvH5D7OF}`yi*w|GS=_n4oq%MFu{i5+5XwE?F@}|Le%==QBk2pL(sUktDS+M` z6kx{&uCwdW`GUsLs3oz4L_11`F?P@7jTkV>@<|Mak>L6c=rgd)8J&Z6P0aIQ9ZP*%{e|GH$Vv;%+kNp=-YZ2rI~u5 zh+visqCgHE*Jpz~)E>J@Tz7{M#uBw)9~&%)Cl8io-dfxJeaXu}0GI!R0j+^IG)RDJ zIv~U3&52555HDSF$N&!+C5E9qb=+o)CKy2q2Jyhzx@*v!99#6(7@sSaUUj}n$P|uo z_>%YpNlg-&!j$0ytP!h5+(BR;~ z{>LCQXXiM{A;;;Ic%ACsU6Djs)q1!QWv<%l+hck=EG9|v%A(t$#&>Kfl zST1ODPIhpw5jVE(j$u&9YKLU}R3hG&%oX7e01knGodZMv*VksLF+pWRm3XG<+)Ae9 z*fLfJVMGO40~D)dU4i0~+50F1UZ@3o7nMJW%L6o1_`WrOx8C6z5RkRVDS{fmR#!aN zRF%zIW{qQphKQSVvnbQ#5YgUUVsy)#Boj8YQ^B03iZZ0r`!M;`6%haQU~VM&MN91f zg6nK~%8z5Zw+2cgCb_8}=N@)SE-i~>sLGd$NR}gj zUko)O?j^BQQM5BUn7SixC@~H1J-}Wuo~b+j2sx>Vtz!H}Bs`_6BrD0ubaW!g=^4M^ z_AoZ5zIx`4F7OE+!`}o-%b~Nq+e|`aU>5PvHcnta&re=};^T3YMt?{{uUyeqHxA|0 z@!2er*(AsnFtB$HII+KJ&Tn?IEI|aBKv9fPe70sBpZy&j*)rkY`;N$4BkUf1z*1xa z($M<9@MV+MJ-U0)*iU(j5X$gug8M4T@OS1UNicg=`Cj<5lPz`>4KQ2Xeo_yod8>I_ ziKGFDK~Vt?GSZNPdJIHoNvxsag6xr|Ylo4WI8zV7lZ@0B-DIzw=A~LE9W=0*decKs z1AVM5w&@#RFZib=@Lqa?>~Q@OUmwS}exJVsp8#UCDdcKv5PshegB0W zRhNZ;-VJ_qpP*4>CsZY(2kXHc=7*mSu#<1wTJiJ`(aC(&Dq}%e(LQJF9NFUjc>mK6eB;iPgAD(0shUk9G?(wEvD zSGN@1u+0qH6<_$YPTdq2g-QfAH$1=W0sf%s?GtG-bCsYYX_%^yJSr~Ucdc-XLFXkq zmOGi$?PDo~U7m=F!KeZ{gU;ENWS(u)*h zLaGn++MWUqK8vk$ZjoxrW)sz{xLfgo>CWlg4nZ`~>$uW5y8!oPc4%-RS>%E<3QQyS zfcKF}D9WVplqyG?u?1B;qyM;vDyAD_LI$M3L|y6vhcw%$tT2GztZ5JzAp#yypLRU7 z1{0)#EsDLA8Eug=28jb)W8oX~;LjD!=$YIgpQ(c6mM)dj@%y4u|EP_AcgUx9{8dW) zkdb78Jw`w@WDrE8{yw&tfvD9q|L*dIKnkO8=J8`DN}EXezTv~mC{*JC+pMd!9hG@BBc09&aTIsnChSI$in;T!3=Hy-q z>*%ZjPDy`ys91Abgs>mKzB=kbf;RGb$u)1B0~eFzw>@11-&L}%6@v61T#fo|ADObN zO-sqn)=WFGHS^SJlQ#^Lcj^F3(TgXVs^KR(SbYboq2BSze)jZz@ZhymZlp4bF$7Zk z+qflHLu$xWB;xA%bpq8!>pk}P!uOgmlnAqk7gE#W>OUg>#ycJyrpCk;6$YwSXDp`1 zsGRQv(&TGK19xzP3Mj~8MSSL0I!at;`Ss~gc9{vvHcBnqK8$xg%T0Mx3n@h-(bU$$ zxz-vbzuX@WoiNzD3qB(JCDvGQg$gWvAl3+Wg)fY#w?%}pgmt-YW!Fi1{i<=91vG*( z4KM)4hm5h2HW%Xe0TiR=ID}-tG}302s(1U2bz*~!poyAu(Zsdnc(W>dz7brm z)!@cTEy8k~3+tKaJo2l+?kp-bdn+)7G+wC>(v4tYrI^X5^DxKlF{WTN%0nx{IOU1N znjeo6|VZtl`@`VXDSj4Th)LLfI|mocJS$IH#Ca$L}xgRreFGB2;BWc6dCe(P{xA= z2w*{6Y{&ye6wa`X<)wJ%SX#^idj#+lv8}np(m+*w20{Ahw_};XT_621k%B-rD8uQ7 ztvJ_nRC3tQZIqSA=z{=S1pwLwh*jsN3|ee_l*r!_Gc$eTX_=P{I0rlSw0I};&T4-g zYH_BFD8p|w=m(>$=hd*}p!E&nkKgwx2DmdR$3wSfxNqw;+6Mf$OZuN;B^5<>A7XO16d%q(v5!xm@aIZUf)Yiqe1Rvivj25JYzEmO&niIQ}i8iUw*YD|&AAdsq=# zAmq$DRHvlXCXobwrj{NXgwr^8qz!QsWRA0?pY4>Bo;U`X-h!-zLNbERH3Y`*k#@Rx z^>tO{ZBEsmv7*uCtXh$=ud>d{BB$jA_LmHlG*r!I-3cOV3UenVK78z#<1 zI3`Jr2U%ynV}~6_-_HjNuE@V~yG3dDJHM7L?rBPF6msM=ZKN)ko+y=ivN#$cRBLy2 zWTHZP;N$q%df>WGd#p5kbj~uACUa*$n(+{yuuT?6NEt`M+4z?MPOt4T-YLa_H*xT3 zPmWelH8w%2jd0j`b>}5q**J|TZ(93ql7yZp_Jpx<=PbS8X_X$rl*R!hmp2D`uLoT{ z9JwdyDKiLX>*`VXxFI5>{BZ8vg-Hs0o1!fKd$)$~>tp_YCZzZGy0^l~V>$>G_v&Nv zrKe^YY4$UU=(Q;Ykx=}{7V zL0C-D+4wPgPz|LxX`5VX&~T-Un?|+GMSaHzpk0PjOohpjo#b8Z@_U)9R&+bCJ>!FJ z^E&>e&!j_Vyago!95S{5TeZkpZ5M&0PK=b~i#I(A-<5SBIjw@`B0Py2qL(URi~&}E z2HG#jFoQG+PY4Iche#26_v*dX?a___4dwy`{!ci#n}j6TReBF)!u8S}kgRy05lM%* zD#`(^%&Y++LaaTv;FKXoL-aXspF?_aI2lv9`MzQiVr)3KW-0|`Km{VkT7G?b`t zy{vF~P`HT7hCG11LMH8@_dSJ768P9#JZf=Si=eNne|;N!H|~Ca!>QR{bP#4}hVT%HCm#*ve?RY ztGZ!TcK`mZF+k{>wP0X%rYsKdr})-X$l&=3KHSma=c`aW{|c^Z6BMkTqe>PpolQpz zbMq5ol_u-$7WWba95v_hi|+)@vo{ep*3xF}vrTr`^G0()4=XW7Nn!m+P|hr!anceUN@5s;_H7&)uyD3sErO<0?)8H zdWCV~VWUUVEM$;EBwVWKIqW$R4Wze@WWlJ_-eNCwG&?<2NEbNL2>oAYq80S)g((+2 zBvqt&h9p;V?@cyHruzi_BnN*90qZhC6R)u=_V174he1U`aQ@Q8G-O_MN(*fY_Hft* z(CH9amrzUec_hmZ)^GD%q0c|qvmvXJ(pR|1ap>fL0B+28Yy-4X~M1G#$;?-&qt?X4}+_0-W$ zGhdeC=b}zQIqSuCgWSEDy zb(Yb26xyz!o@V;{2LKBV6!^3-iV5o7Lu>j(kr|L~gTW70tod*R zb1Y8}uO53n;s(3~)MLW&LCJesCXgGg8r1B4^vWaqV}SxO2a@mT`>u4(h^vp!9yK#F zfe?IWMvc()%G1B~Jv&Fsz;S*Z?UJn!%q{e*`1jZBe4zq9BRbXKH$&e&XlSEPtkGTG z`z8$%9U+>|fnt_{iPC@ef!sh)Dhn|) z{p@#XJ@spyd2r$8aQ!HwE}y%1p`(VHmTkiT<5tQhu?Fz_h~AonkcjbDmUUx&YmRrx zTSnxedqSajI)>}sVD0uVoYc)C8&}s`eX3RUx>7RFe7$rE`Qq;5h-iqA@__0f$TjfP zHY*%+FJ>crXnfYa9;jnCVn%)m;M;*M#@C0=UGkLvOnMKtNE~6*)V&nA*u^dI0+PF* z6fG=1ux4soy-h9PoTS#AO`n{Fq777KOZ5g{6!f>^4OWJ_T!TpCa0M;AH>0zCui)KY4h-*H~%kMF9vZRMHa=-#IhjObiK3M;kb1} zdwz0gOA~B61n(Qj(PbLgIYH1BKMX=9HX~9Ztkx48IHMCT#EGYE=KHDK#4>=(3Kj;5 zV1JBYu}7w@S6!jKbQh|L^X3nJBgAT`&mp^_oMMAN&?RL*w~Ak)vK&LoMu@7$3h0LA zCHE5($Tc$eIzF@dTt+Z^&FoKoj=!)(e9OwBS|No+ix?6dPI`|b>8@C=t2OsC%!g#M zj*>^o`kv2Oz#wazk&7GdYUYn-6gsz}4BpVmjDc}Yhsh4M1U=N7YPdxU>)be-+_ zf_B~ORuLqurX4~wn~M3eWzEzuK!QT}NCv z4N?itsX{&>hHXJ&o5Acq!>ycN!0GQ|HWGF}|hkQUKbF0VYe%5d_!t477*%hd^0I#bI z2##9|huP)q+nF55CJjZjgYtq?LR&ttGQFr!ao3Q${=-cftpVR?CVwon=~Lh-62Jk+AqG zY8rK(a7<`cg4Jkg5z8R5%;NT{NJ&hUN3@w_)@|-ZFOlUiaG`V{){gVmX|NE(VhWU* zX%9L1NhfAZPX|;N; z{24S~MqqAC)^Rl1!SXC(O+HNPEhd62XTih-!t7*?zMJTAHUk-fW!)~V;78-D^i30N z1yf<~@0+B`GgdUvR!YRZBe#jKFPX&0mj_4DN~$bck0x=(kRAysMu{oY>I}g_A68P> z4>eIJ84h-auA)E zl8z(AmE4&?abTHoQT?o`t#@&w%v84zeLi66^K34{P$K*?8T|NdF!jlINP!6RP!~`%PK~i> z92M!vSy6&<8mFEu9I(;IU2&=g+Xm1^D?YJF2VpNj2z}2;^p-CixM5Gc>bx=tXCirB zVeg2EYN0xx&w7oSs5!AJ1!$u(< zm;!6DTQl)t?zZGz^_wE{wW*7}woSz(vlONIJUGW^2lgMC;$=iZuvMdearbCMPti8%mTU zacK6yqIrtgYl2$Do?3{d9oB@!v-gtmEt8QLw_PE1xyj6q%`o_CQK`JWOwtpS3qCGh zQE@%xazG@LSS;YYOJM3+6WF0eh_lasw~uDbcQzz7ZGHPak|Om zIg;2`DUsF9FgWs*EUnb&{A)g}byG<0 z1iM#6nH6X2(QzGNb?5lXunxDsz@U~9Ur4N8m{cRxV)EG6@!5E}a{TwTHrb`UH5W~T zvdxi7-1v9NFsZCCvVlTM@p@)Y61AbLqs-87gzdQoia|>GMu<&%dQQ2;tfC$Yaa0ji zM3p62I90E(9*U7DWl4%1W9jKc>-j(asHg~FX^=;1{krKIVKxKNM%2Fs_f_n%H!Lu` zOZtkd^r?+w+=eBhU9Xu&J$uR#yZ{w^?~PD|*f0{$OZnhKhF%!{bd@94K9z*tEnxqB zmbv8#MU8$&2@=q7z~OB;!anMmU)cZV$4K~`4*>w~b6tBWDm=vEra4kqk>bXCzd7bp zCy~)W`x(xCatXDt_5srFnE(F?a zIa2x$-5ZUR4rsejQq)At)l{#uq2|s+%Y9?j5DzDrQl*{}<9RhiRSs^>_D!wZ{LMO0 ztpGObuaHJ00%_3bi}vbN*^u1tA-1Y;acD+&!xa-=hV~b zzZ#%5oG~MAM=RD&(q;lQjnJ-|9VATKa(IxPr&V$lKdM*ps{0n!ralK3Sx}j-(+B9D za6aXLcbf`n9?N#vZ=s9W#Y0C6tH?Z2)LyTb4jHB9D0vDkq_q-f^C$`T29^+=Y%YN{ zszxI>GKt%0w=X3u)Xusx&>nM`{gxy=__}gRn%ays-sH9Chuj=%Mg>o}DuuSTYB1b* zRO-Lf&uXrtGMgtHBs>VshQUq<|KfltO>0vcC|WdZn1K_Mz)CdBza%>7+KCUdHw~5#vu52Hz2AW?KUIj7o-4{(_2nCtNyqhDzin|Zuw&7lG7KWC3kRDv?hlwhg z6t8WVYdBOb7zS!j0Y%n58Xo`0Tgvin(!t=UXE zv-m>e3(e71o$NjQoU)|vhye2qKLcR*zuTPuCzAqR#4|f(XYZVRJbz!;U~7XW7jVs# z9>nM^dK`T-^Yj{#=fKR9NN+PsB8dD)rJTYJyXwYbr>lJ2ezRUQEhj>h_x>MH$^n%K zO~pb8uusIICHir=h(!kPWNZeEDptsMcqUNBwI#qM+vq4Z(*JEaFKt zn8&3-#*V4cqVohab7S6mnMxWxM(RK*I!h@Ncf2|6P} z%o1`Fu9nO*W<*wYCWV13eHK* zCP9M@9jBET+Z|dlPl!uyp&JhC(HBN4q=z+BxM(T1-YEFTy=`vCEb?foTN%P&Od$gG zo$`UN*9(+f63LQMzBv1|x_)C@T#udd(HuSHw1_eg9wFpFajyFe*8v;3==^#{adQBLBb!lnGl#ug0^E5SjQXfGZst>uvLd`qk@xyv52zD z*#4B@eF{b?7%+izZFkiM9SDz(t8$);A9d@r5(+FugDrYVLHjoKO=*oy~aVdyf_{Kebc|H)j&hm&dDP zPQl6I^Oj3@Rrpif2$KaJDWyHzb@Y@`)!N|WwPh^^N8J*Bex`rz_^j1hK^&b!j6{NA`0S@=Yx{Ruj*c)Rws}Vav1sM%Oz> zN7jDZ!m(}J?sROUW81cE+ji2iZ6_Vuwr%T6zvsK>oN<5e{ikY-+Eshk*kjFFbI$cV zv&6L9cr7Qj4;ZvvY!Rv-NHPj0<12q*f{<{i_6H*4ZBUjX9%=^dL-8uh+xW#AF?LSqmD_)>w5X>nDn#IsPN2bo z;>QA$YHKy0Z9x%JXegvgwiH*EVR9#d5<=4&n}Nb@HK@{$`K9T!?yjFD>Da!693H9` zZFr^=@%sYrq=~ZarxSxTp0YCd_00ZGcygJ0x>|irxEaBK2=qE&OCvJuMhH3%PV5VA z2s)6nVc|{c_^#FoSof~jXvbh%`x575pX@koZRWmNQ?;#@Z;KSp4gU2ssfMFb~^ z5>`NJM`uw4`aM?dhH!vxIgSv9 zt1sC>+yh(O;PPsoIvpT}3r=XA@0MlJY%N>VG+3ni88jaoIAdi=r5KlmMPdDf%2$se z>4zCoFn2z^Qa2})u$rVIII~_Du`ktq!>*Kp42_;%^rApg$}9;+fPZu@z;X*cFK*33mcXhS|&FLv~fehSdn4bW@U?Y^{m0#Lu7jVU+l zS}OIiw2{^a(YpNcuw&ZH=n||)lYHjG;+Nzgf z7H3>5*iQXN2#%EfM9@0ZRA5Dd|MoGvoJdh&_~Bzr+OLaj_J;kWi6v>8}`A&oI$PLv+cB2g|qAVho+#INZBX z{pU8L6AMEYpD@wrgx&NK{l4R^I%U=V++^6pw{lv#5^oQf0_t>1b?{iOV_i&BMzsi^ zrk!_u;4ii&i`Ua;OM906oa(O)J47bWY5O~#S2?9eC?iAhqP6_5r=5BmGe|3ZuFBPL z^0V93xwi9QHJNCTqseQVn0O>cZk<_= z=ovgZ(gi2$w@)z8Sx1;D0gE9j4|K7DO5q``ONNygH%FL>g+=KI*Wq0V{6z0NH1(mG z{g{&`lM;KKN93w|F^$KeY@>l5ClVdP<|uqpljXI!xha2ZY<3=JannJ%C~QQ+#G+V4Btx1seKU*dO2tfRqQ?q zi*3lp1uf?IZ?Y1izBo0Mhh^_+OyARJEyOJLe*0uvQja)WaP9+2Ru`_t7yHcOE4*6{ zo*6NUq6tnPrrb;`ro|+lvPhE}rnsVei7}ST@xQ_^$9R95&6aRX@pFWonK6(viCe1e zWphh&H`!y~!b&V=;zioahJ~yxYAYC%h<}hw{(R?vsaqfY4e-;H3G(L-qbC9nSpq@P z0J_AVP@}iuPsd#C6HHt3hSkyeG3u%IV>}X2`9<#!pf6;*Gcf^;{H)(2@p}(K)btH+ zI{zdCj4NAM?}{OGg&(}*T3I&dnDX_JjUNsf%WZF&Hkz-2s{XJp-w4$6nIL=wV3&xl zBVVa<=KPTs%P`Zy2jtSJsr0d6aPQ5!t)Lt>xD4ydqQ8#fXi66j%QT339%<2(m*mD1 zZ$H5+f0&#sI0wi1%ikTmL1SBA6Ro_=fN2fOHR0wr8igI54%;3Dr3i}5??S>L3EWMT zmxoArsj+Zmq}JL}w#w0F$HuGS4_t3_CfZ(AMlve0?PlPkeVeGd#@hM~eku0O@es1G zJ#iFw^rGKT&m^%}$1y8G0lmux0%VK8Cj@=bY<{F0$-5FanDLrRwe&?)xK3xT3Ud`D z%JLLJqBrtd_eTcJVE|1t?6VDo_ua=SJU=2YW=c&`lIVkd@V} z01rv`rB0bu1c!V!`bx|fq-LCy2CUelHi!Q9gcGQ#dj9m)$0BIUThPg^baucIAkr3Z zYN+FaIs^5g!Z<@-{Gc{9#Yn<&_@Fb;P@j1~HxPEXU&5$Ph%r?B$Ew*^AZ;=@l@aob zz08X2w!eWv&P?Zil0(~EZN#K(YY%=n5d#5qIix3YGQ3=hI7UEjN^mt-4_BV{t=rKrQ&(*=Rw(Z*+1 zIQI9_mT2ss$9>ce42aSC&~;Bt@PARY738~++oYvV{vQ(z%P;QP1|j9+2{m` zwu|wR8N>T!n@U=vjmk1M4s{hX`oDo*Td0mMY8kN+40I){1#6T@29z6*&^w4;(GUH( zTO8{O#``&ZB|B`-jC=21lfLKPkWlW2t7fiTc~Fm!blwpVc`#eWB47QPi&3G=Y>kwh zp538ZX~dRivPWqoP%}Y{j`-E12|Qu92U`IVV}-(q?5b)N#B;2oWyKn?cmgJx&$MFC zsJwewq=&(#PH3_MuDz{G83AP+G;D|=UBJbAMfZ!lh-RxCytHnEk5uG4lImZAJ1(B& z3?jdeaDFe(Mfg9#9mX~`jz;!Oe-n1bQzZU|nejb=x`PXBpo&q9_z*hBayTR8@&gTR zIQ5G)3@9Uk#(x?uaNYy0JYuo`l9|o(ZYn4FI~7Mu7P8-bXA^wccflbB zH6y@kT{oENu<=Zbj%fpn4;R9NbQO9V4F0v}c>TFCGMYx|$Hx&rN#*<&T&;MnOQV$& zEQA*_j;eW^#^LN1`eHe#8Lpz~y39iR(=8m-r8F_uTF=0X4#651F$U z5sH=#=3;Lw+U1Tlrr{ERUr3XZ2s4~WV{KCzi1{LrpAxBjIo=86(dvVkbJ~2VsClCJ zhI_%RGt?M%<`9$r2qrqA+%?r98_Dh)5f6~9BucvhpaQmT`UQ@l>V0RhFo6<6U$6^9 zUVz}`OzzGB$|frskv-!}zJPYdWx?%AWo#}#b7O8*$cZ|r{6XFCU+3~S4wrNSSfwQ9 zs?z|w!^9``Eyr+rT3C{=?1nG$0Lgl>)fxlJ1M(hKjakKJ%RuF+IR|tw@?$z=>c&Bq z=0b#W1A$&yt)ztK+m*ZOc`J}rO0_Th0PP^A?G3wnaX;HIf!o(k7jcwpE9|AyQ781) zdel)pwzme)0XhcwA==~C@@&*SLrA#~UP5+mar9^PzPQ-W==nap9EWZgXwDH%tY`m8-c=nUQ$SbycFVxIP6^nwMhW@weSQpFj4N^c!rYBYgAPDath}*EF=tTC; zMiy33%=idjjGMTTk;EUmlHL&-f}NQ+@J^(AU7dQn z3CkxW6hBQ!Eo`W^DxIbS)9KEv-r%zb*(|kPN?TeIp!|t$uoJ{YxXTpAH3OnMpqBy4*b|TNDpS*3|BaDD`?Rs?GKV)`05lu)#0(KpEH4Y(y1~AWAv02)wFJ> zXrJUs-SL-~?-2?I{Cmc8eU2un)`m2OCu13ZtdG1$I*bGq(nMh$umo|`sQM_%+XQEm z!2?@_ITTKOjaZFfKs#*x@ z7Amta`$q0qk8V5Wjv>&2l{3fW@Fk|Ybx?DL;-dBqa5;32-)BYjNt;#1;6e+dR4d~r z23CHY0w%o~Uvg~)FOL%0S;$Q7ASbFqcFyHK3)sVDcu%|Gcxr)Wzqyd%pMb)C1jwG{ zB}-A28aDY8hKp03b!mDP^go{v+K(k| zZCl0+Bbju$3#b(_kaAQc-66JRSi|VRpP0q|x2OwXrW_qz0uUds(biSQODcP7D-+jv zl(q*ehUqSWP-VB&CmHIfeK;$)XSYgp@GZhH3M>bapx`qa?e++j^A{%fGxJ{rk-}2U z!=xnycOow(elW!)^+R#4W}KR*cREb1?OxumVBJT>KHXordN{;*v;vf}aZ+ExOp0y? zqR-S!&;RUi`M+Zyz1+MPhQD*{UZ4R0zN75^D;Mi;d$s=Vunzw;*yNGF4L0}+^cBwk z5{}djT_1cxd0(020+<2C3K0=KHZi67N;V^D7}oGW9$zwS|I5yJ81wY?u;-_ZXrrEd z#rrkYH8)kLWa@I&J8w6>DD>7(vyk1F%cko36{6Lk&KX%MpE1o6eJr#_{|x$IW4kcddMdbIl<< zGc_esiRqBjqY~1IRJ^N%HBUsNHq-D@Udzl-n9iDMXqtuYb$=RIR9+&t%X`y{PnOmQ z&&L*F{*i-9U|diH=Sc($FoRw3Li5F)I(73veUxnUNosnt$CmElIaKi&s-L}`3e%f5 zV8#6{cmHGOBfXInNBY`qhd<8@l=WQA)0}S}a2(>oO>I6>cz|y|aU;6ly+~<-(q+JI znX*Q23hQ|$6^&qoUO;GX6>f>mEy4&XqabDHlv_YLf>yfLEPfF2#0X9rdp)jsx3Wn` zqAW-K@Kne^C!afO^la1XM6;(XzFv!Ad@0gRyw<;LO#Hn8Reo;?hQ?pDyd{AUdLmN;4$D$okkP#7mRYFP(Kg8r8UHi8h zPtR2P&wh-|hl6_82JP`z`hQg4|6TX+i?t_zzU%((yYBxx56;Ni@c&izjc#EGoTmB> z!F>G9@0%$Bty6yBn;}6^T_Bz)j;ms8P8ETAEjVD3UnZ|CigOw?L@T)(+!h)Og~6t2cFYl!pOX7hD7r#BduY;lu{)kLrM=3 z7eusZmgTB?s8_a;ilO%UFhym;Z=#`bi8vnFUu9BfCUH*kq!#O?U^SYL{muSI&Nj=q zt`g3Y3Knb*%>J1I06dxXE_xO})%=UfYRw#*V;kY_tkb~F=w(P~3%e!*ZH6j1_4+A2 ziWElLw)whXS`%amU)2+fZyJ~e0@qcIDN<>IuVBo2Yy_-GslU{Dz`l;I=CcRuc{3H7 zPd-urM9(0^OZ5d*f}BizXeXUpKsf?dy4E^j5b1;s{D}5)oNkx)DMz9V{})PKzEMs- zx6{FcDX*hQ&FOvz4aTd(U`K(fz=9Ep_o_qQwFMj+e;Io$t#m8!BR*V~SE7_V)!T*k znut7GlX+{#CFcbD;317)e(4`5>^#lmI&$yBlsgiL{N-qF{v&-v8Wp|$b|dUN+$9deVSv?M2Md9zIwyq?ekLzFv-gmN?&tfwRDl0*wzV3W=U`{rTk1Y`}Y zdeMOekus*jaT4ST;;OGP7CzM6H1A_YkGdUeH4N*h`l9`frJhPD%cz&l@rM4SWHDIs zk%Blzzw_BD%L6ce1x%LspX)&NXkVYU3!XYjHhv{Ft?6T8>7u_JtU_v2_@o`T;wsM( zna9u}ed5*26X}Ap0>eX1sM&T#4-^AE)AmHxMDs?3*HU1HGObv#+YL38u?>`|O0R<~ zo~Z(Vp*n!&s?i7XmO3sEw&m0^F*BWplrKW4=~&tZI%P2>55G#;s_#kf(rlhwI*C81 zjnG(`-kh196?Nd9V-|y#OHSVT=jo6i682FlYVr3y(8WRV+RK8JviMtK9#HFE)tp!+ z@YWevH(OZYo3hj?jj8n_(t-*i6shhoTV8GV6QydmI*>Q_ZB3}2b#(M#_5Fz5q{m%? z1=@OZRLNi&#p*0&y)kaQNw%^ZlzO|HNTXD#m2_a?@0Ndx;H(p{22zo+0_4MvL!m}h7dVs zZ__=FpG6YjNBsMO!d4!Atc4D7%q;myIDJBV0UzT7VBnw6$7(IJIC{D#znG>(=$Ig{ z3AP1hA;AoG?UqXm9rT2R?BGC>JV8&i*)Hc?fp#3vciY-}<#^&6xS+;w-~I3xrWywH zS|K1}v7d_y!JjJqCQVk3C>M$Q6YBbwE4?;?lqndig|cHQv;7S>Hd_D@#G zm>vLhDA}@SggUdp##1l-5Vx)_&f?CfC2y2AH zJYEWKXP9T(Nl^{QuYd*9hH3bw4$$P6)7cym8Ln|rIP08Lume#+#5e7(&7P>uV?J?r&0VZ6lr)cduRYTSSp`uhcP-6{Vw({m2s+fn?~8hl~IN` z!4^xtL4c+Jz?Pv7QJ-`o%W9;p3n|KB&8?YCTwxhTLWlTLBqyY9^Sws!5&>lww{okg zw2FOANHqP4W%XWF+vPM;At1?mQok3$2HD{#9-_=zS7Xj;m`&~V{}&?PGaG6e4yT9= zNBpO9&GO)S{xWkjV7A}F(=$y(eIqEzF2ze$KA2B~>g&)ttflVlC-CmHY+xLBT}5XX zO=gkgZcCBnlk|cHOs5A~DvrV|T{PW1)Q$sG##~(+BLVQIm9%~~BY{7}TZ0!uLqlN| zLh?w1ut1Pm*4lo*WRHsO=yl2M$U(yz6TKRVx2AOa7ByPmpCVvhs6B08aAZ9$#^8x-_Xwg(*SEALrYd zm}_4f3X!??_{W*Rdga;OTulNhRCCB4m!TbfhWg3{`*$41>gg^+edBQUfA^pNa`G7e z_MeJmt$+H@7Uvu%!#z8q?vQipa+;)Nvzs}Y89V{>%CuN$Tp;i3{I`+kcEN0)m|Z~4 zw@hgUmhkEqll0ZCb@3~Lj;=YA-GWMqXtwq&$Gu8Wf#^4hR@6qd&LKJRk{nhsqA^jS zfs1I82fN$Xr9LuUqa$BdnQcN`Ew)8g5CkQXLi7TqOeo4Png>cmoUj8%LKLgONZIWd z?VCh#{u24^Q>m|`!9Gm;Fj@I>$S98A7zFMPJH?H{ zB0yFGp1%U|FTpCPw=O^@V?7puN-xl3m9G}Zaw29A2kKR;xIrT!4FWX%gH>}p$p z8eMG#bvX*PW>#1=H_BK~x-BJOFD|943nnp&6^$!n8#%q~X`j_)Xo`KXfK+ub{%Rh1 zPu955skBm0)`Pc1_EzGY939pnf7OmK#}cYEMecLRVL^hUcD-s*Fo%mjYqsP;Ltp{` z7SQmfp)pYWfv|FA-7l_gO+7|guFp2YQ|mp3_w2#|?n}W7g?Mj?b7;H|PJ?OrIh7UUhT`NLY% z5mLWmd-zbj-Up?_s-;?vuPVp}CgNhSboZ*@V|%esq+u4hxt;+V)y5=!f^;OwDry~N z=6x{r;f6U-Nn`!wK1A?p8?~cx8@3zz3q1`nNe}27grGrqAjp@nP)Dw%lr5Jr;jGE2 zryw7%2Xw3>c^Z*k5FE zKJ2Y8d3m|LeYfWJuJC!Ys0VSlG3A)v_KpM3MS z`2UTY|Ke@`<)i%-orV0Lt^)0)l|HIq7 zv%Y!Tvu^LNw+nNX_T$^r9wGyoAQ-m}h6{%=CR524{K591m%TT4CRW%|+w;B9AxAyD zQl{!;d|plC?``qG4F)GhkZP%Z@?jydB!EV$+4ZpybT7(T$dd?L)`9|ZgF!JkV@QZ9kBLbzPcgN8zNC2`9)$L z3qY*_eA`M9Oqe3>Mh37tbtLQ+VnX5k3C;kgKfLdV>0}FJU&F zpP)%?=ey4(Dy7nzA$1OV4Npj%Q9Y0o^cO)8#Tz4#{Dw^MtS(~-!CNy>jfq?%oy2)` z;bZq=6V>cAKzobMVYG12kqxp{xgZPk@5STYK8cJZ@^8Ij;3{h>?Qh{?$%EQs23Qp6 z{I04P={CYwP-;B^$(gJ)Kw0;jT@P8c-2nc5DGk{-=TU%dZxo)}^Z_~fJ_-N6Ux{5n z9i19SR}DRiY}Ok)bH0U`Gt8yVLr2aFl}ids{h4RA$gDc8Ywun+X~1ojVgD`D4Z;>k zD+SS}KBBcJ2GJFy(3r0g!wu~ITc&gG$K1z`{dK)_u)deKNVNPWC9v%Tf$s3zvub7C zTlf=9ot@mbBxQC|@$NStV3F$s4^6w|be`cv+lJg~Ya~_A24aEIRI5||nZEL#Y`t0;wqbH@{fw^KkrS`@>pXFic!QswY&X&n5E63ga^~sS* zCm-q<)Y=R0dP$1UzsebjjnG$*?{-q~pR|topLUWW{_R_#{P$p}q}LgR(0SVxq60#o z+MyV+eF+5SN`6=5%i&gZl=-SheWN32-de<+xJCMByc%g1GqIXd{6?gr zS#ingHcj3S$w|zZ&_L0y9>krPv3-yp$hck_2;jf&G9?2^C_eT#I!FoYI`Sb+*!1yW zX{&QXXYN(QL(8hp^z_=E|#={ zcR3rwZ`1yDmv=(iC?B|!uM3XwL#WLIlj!39kpI?X3KxrkuF=|Un$hk`7&t=v>nb`DWNJYMWt_>z+9;YM?r>S zm3bxDCQ_-E#M!sB0puSOhag8bwK1@@)==J>mZ$XGD2UHK;Em<5g}J|MORs8HFZzL% z@|#knue|FA#&V&p*pVUT=;@gPXg~NVeKVo!RRx5n#9*ZOD079ekboux zZqYDKpjM^P859~=8IEzeXNyLexn8PIc|mkih%ei{-V-#_Kn;{9NzeWkgc!i{+8kJX|Oc{y7% z6UUeCOkJJrC%gop6wKIb-sB|wN*-db#){zJj|H3DEEiU zl-aqLYgZV!uxZ(K_wE;aj1Q7)M@t19R zl&kX8VL;+vb*H%QewH3lIECaVvb<|bNW)O_iqH~7>^9z+d7kb~;lvHP0>NA-n2UL4 z1j(|OAtMzgo~`)!E#D(t5}q*EKKH?N@x6+#oBos8465%DZrmngAvdus)dNE^lkShW z5IZr@6wfj^%9tNH(t@Mc@zD5dMMlOlrQ3<(kY!*bY6xa1Y;ES>eN4^228<&WOo*as zm7$`OQ1lt3-6?hh2n26c8Fyg?dtqSp+xj;H1u8c$$agL<$vkT!UU8bW64kAZPt@CNyRlD-~5j zlAc;qUf)0xjN2i%PQ|3r+la-IgQU9zpf!qxhep~@i;-$wrs2>RY@}p=TVX2Q0$H(8 z1O90>j@FZN6;#7ni2GVJ(5yPE;$4GZHIBGgClu#9TxQA?cX6~W7o;(yaU*SFLtO^l zdvp?v))nR4YE~`r5mgUfPBD2AOt@t{uEi&R-1g&v&IA(O5Xnzi)*PY?)ChCByXNGc z*2A#eQu9ci_t~MKJ$WWXrU3;Eho015v+vP4cyDh~mw};9j%_0mkDHDT<6-;fboT{j z5j#)w;9?6FRvl%U=&vcZXsf8Qw2aDuwC@q_#^pQ*kYFe1KOrj5;UWfz zT`bffj>{&6&h^R0HA~@=(Sxp;c#*uk6sEZH1o(r312l6yFnbpm^m03+0*cP|8RJyz zB~0h@@d&i#!s1t%?&*V#hq*oSmTWwqujjDIe26exG>R~g1WB6IYEnhI6l{g51gq%G zNz-GN;W=v-C>iPVDvQ-AQXAGv8?nv6UbWuvbJR4)^)Ip%VUVVp1fWJ4k>WUB-zh#t z?u}}NYr{TcTL?7jEh)EDG15!Ir?y(dIPe9t+K5P=Hui1|!oV(X5taP4h?(ct$J2K_ zGd|LnNwh8mypekz9>^=@BXpwraCS2a8@jU|jZD)O>U1B-%u>UU*j_3_k+F)kuR zjrPL1Nu8V^PC9(p{b(;AhxXL-b*Aa0A$0gDOLc<~Pm&8W=75IOOS)C&(1*FZW+g^3 z+HxIdu%i5#2q9gPsB0d!$DM2(MjT?zq_Yi^$UH4f(jzb<*k9uzNHi07v)rEAd5suzy&WXAMJ4V9Bo8rmuEqj+Yq z)G5%7V-Abu^b`7!4w1V=tn8PHW0+Q6*Di?>ld{`6_s^QN@ zfO9p)$}yyNgVK-c#>#LerXIS>lvV?|oo~dho&H5Eb6d;xy72*I0P;Ep9!h$`_9||M z45+QTFJvArFB+;+nPf^q4_P>k|f5U3-I2-UR@mK z>b_U2@m5>RR1ODGd(<_U?}S_1VU<4KYS5~0En9eKx!O_R?r7r@UvI7%JSTe=E*JL+ zxr>mc_AKf_*`R`ID3G%v2XWv=Yjm#{a$w#2hEXy3#D$CIPN{IoVI;~d6znAjXD8pb z!X%Z;T@<<6%>mb@6{BUm+4y+poLD#>p6v8b#kl)XC8IXoalXsZe}32ge_H7-)CRYDuQ$%GxzqL`g4MRuq+apa5Q|6<@i+iOSHcFC^UNO- z`W=c?kfjN@jgOigp3(=}Zdy)xj;mFEP=2y3Ea?)W2*9V68wRpO$lHTXfQ2c`DN4RE zp(j0^Ii?c^9D_zUlRxJk^)9#Rf(isFDqF?ukr-JUpVX?VAPaRoUz**#!ZM9}dU404 zqzaH2?i7_LJG$-B$#_`>K6{rXvxAJW_B8~_;DA9*@L~CayFGQ{p_3mn-m$9}ADx)i z##WWG%gYz7KTFoV=q?t$X_XtOJHpbuZ7%OK;MzX&O2bZEAx^DEGfckjGk_;FB=;u; z2$8#fPxZC*OH=vm6hQ&En!Vw(l7Vdn%_vkbG!R~d_}O)Ei{V^a!EioaRtZt9YWYfG z1gJ2KQH@t#=t=V_i!Nw;D89lShI^#0Mfu60nomjFf;)89;DZg?H9fM4H2tWXO$hi? zEAN5Bp)XsQ+w2u^a^gs4J91^RElB#{Cq{-RWrsRsh^lY@5+yULPJ1bOJQ3aGuLNKTm5v0)jjmRgQO}Sg9)IK=~7UT(Uh+ z`n(H`oS|h=mo6bn5>QroM4x;eIe2)tKT71RtKoxHeD@RoUzuLn_db2%Z%9O7{V!NJ zni^Rd(f{-KkKKN*CKZ9riqVDsivMRF*Jt9{AdGd{fYYo#Bk-JzRMbr*Tdo2VQ+!hk z8Hkh}4XV)WB+qZtox%zj@;?4E-AZ8<3{&=) ziZl{XG{RnL5pJ=2=`KJb7}FoKdAmy-I0;&CTfjL4r1am>KSkGD>)v+2Q+A?5<@|jc zWM1IT*=iByvh35q>o9L69K+>TSc_=<1eDbj;V`+9y^-asl>Yf$UCwQOIU5CT=jm zA#jZ8vNrl&e20BXx77ssmiZ5{Fwvl}rOQ^(a~yIQY(d0_;`RdXpAoVkz!8Rn&X3tp zAC3}T=u=Wege+?p&Vx^F^qLtU$eE3Xe$UKKJ8?J9XuYZc5V6<=9OnKorTL%)fB|Bk z%Z!MAaKF&r($Hm=)oyscWPN)m^ILjfV!hU)f5UJE9gVmI^JcqO`7zJuHdFtnKP+r<+71z#Spj@QR|4EmI|*WF7FFIR0(J503WGodsBIP3I} z7Aa)*A7cQfe?GGY^k8bOXQYVX6jE1Ir-2c%kFg!ZazU2_jX|y$DUn&W7goFiuKCA} zfi!%V_Wl4|D*SR!R6BCy#+Hcbqlr2Xs85S-huw*@vh<pFaDt%#9Es7u}GUk*Lvfx|d(r0L;?aYxV)v=0>>G*`#e>JJ+qTNjZ%TnnA})SAx! zbJ!IVEm^RR8oR7Xq#t-gl?j=uirF7{Jj_j@k3AB=4jt+1K=>1vP@tDUD09uyx~`S~ zsVx!RWA$l->qngAb{K3cIORf|7sri3s$^xR9oE@$%(D=k9E7!;9x49>Zv27~o^BiS zhRYAeS+B>L}%u=iRcZ zbrOxM3j@hvzf|Ogp`Xn7z#DB;mcM|InM~{4ReCsEE?6h(tZI`C|}%qQm!7Dk=!ndI6Oc!IaZs;xhv8^ z7&d(c2ePD!|F$Nap~0FW$TN1Pqgh2ms~rDOMmx2LLv8&6826sPUeAovw294S7*nv; zF??G8*-4vv*kF--()hFLD8VcsuxVWqX_YZjFLW_uihb!q=@+cK8U>3Plg1U%Qd($* z8L;>S7F)7M)ulKqV5W0sZ3pJ4K4a3SYf(n5kl7IAAEN0mVYQ;j2%ddAI0dbjcVrp_ zU=VWq$}=;9lW>j3#qmQ|{*gv%bb9+!2^mJL7&(HbhKjN}dKdi*F9}=ayr1H)1aL9E zq2tR|6u<|}C&`26L6n6}`ImS*S2Sl-uVWEgGxV z&YQrjpLU2A?GFcB5VZ#^p8mb7IH@}K`h$Eo925Wmy#GVCjI8vH3=Pe!O&sWLERAIJ z&5aBkf6(chS)VIQS!xI)WN(;oKGyH7mzr3c_OpEhP$7fJYM|&^LB|VsFYI za}KBSdtbSGG;wxuHa0PS3b_I<$M<1*{@Jh%I<1?32&f{6uRyX5zFpQSSH5t5sgC|d z`5v_|YjUhen&k!`uhH24{IyFuyi*N9CPWxAQP2tl4fhlIiTtR!>qn6LZITd@XKpFsX+6~~gJ_OUzbdgy(9dSzuE)Ua4oikXh zq@`dVlm9TaBh3LiYBSpU{r<=1JB|U?EQ};)IMYZU_}D1G4l$kr@K`({Mm|kI1b|iG z9=uqV3-DV=-%c@LF?eEFB5wZ%nGwuBd*1jkrbwpWuk9^gd^-D7C@!GEe6~gTxov5EgV(RZwed#WJ2_44_am& zelBb&Xqte|&+iV1a`Q-o%`bt`iVVlO$|D>7IR{c#^S z#&==00jMl|FME|6e=+0?YLP>%B`Ek&G9vb#FlI_z+puPms@WdX-l|f(idV65FFzp| z-@4y5_KO?E&F97f+x}JQC3bH+CZ%PxyZFalaV`MijRiiiDR3SBhit@yNOz&%r$*s_wmY1p;9>WFIs zcaaSuwiM$u3wTp~RAR4&VPv(9`f+ko0?hp0ne+Yil9B9lWDsL(Yb{hoFXl54U5(V( zMn}KBfMLBdIOo!+dCmGOZn8y^pSltUZVqklKqUAq#340|aq`DWj6ul+nbqB}Y=06t@-L)FfE%NY6nY^fg1#UCNvq zbpm@lO~H3yKz-@lNUI_skhF&eAgwmi4z}$dB;$JlJIH3J%bky*$o$}>>PM{p!O$O` z9uQ|9u+U{Qr+W*Z<|U`agM?I(rcSS_3-lf^XKH7#mYAYNdc&#=Bu{}wY)C9s$L9ZT$nlA!AUjn{Q zzE4p)`;-Vx(q=&W1RVs+y5XXn)625r_E+w^*sHH~JQ;_gn5H~eCsxBN99gwL1!>+YQQWAf(BHm8hFT%GN$tNOnpY~VhmiEsx&a3x`tnh zH~cDZ^iOJ^r~cFXwViM>x=IN>b=SI zY_0i=#Oq0B^Aq1j=aa(Xajp3ZKxXyzQNyM)Y4CB9vs+`JG#vSPVxYNr3H>&Nf%RobC@q*awe(srYkV zRotR@U71#C`yhUC(*kOXKDVQrY$Ou(=}sx0s{ZjaHCBOU zW9|;!Wz+4=<~PfGclTF&x6f0Km;L)^Cpx-LW*0t8AJy@-=yM9^Rma<92zvFDPG*+R z!_L4Heh)3eeTGW+=i&a#!N9`FL343R zxA*nRRQvSQR7}pt=}f-~d`ynp{$)8bW|WWHSAxgT(-pV-^UHEf_vh_-d2UcnjyFDA z7gy)oZhN(NB%E0BB;6!_ApeK%gIWzRe$IQ%>EMF<7eB8j0*ztwrljBX%*cyMb-;qR zFvGZ=Oxj*6N6xJ z%9duO^rQ|*8^ihW{le+4#7;oBkK^O}J=gdxw`T}X<>X{ex90~@sG~C-3O!wR_n=N_ zPdPn3U8UNE+{)C#(9`Gb&dupf!h*?DHc#i%^U6&B-jg!?*8{l>J|2E`HkTHz*ZW9* zehB=g#s;T0w@01(V?;I^=JK%p8y-r3q_A;O*A~-Zh}XQPow+w^~ntV6*uSW zLH~k@O!nu&&1t<3&c@fk>9O7Oa{{@{6}LwVhr9C<{HpoG$Q*e$mpj5MN(Ru_kl(@S z&{OmlFURN2X}`x%UZqD2er`%k^d&yG2TD38N7vW!=FHG@gbrkesOS?9m%HQrxyn`E zjA#xIoBR7+&*jZczt6)-Kfz(AXdm&rmg)IT{q+#|6HnLE-Snj>e_)7A4v(AryEe+z zUjE)*{`G;!?LfkGb@A~*NDN-^V>F(Hs8c9!Csh6W?n8Ns$&?Yk zih)b>S*~w+PI$aF4=<-1rs8)+RtHd3ZWno?-%GM_OJ(|3aRq&9e@LQS06sg0%{X_S z1gL*G-qq)1XMI>c%!!3AW@qtyy>da^$-4$6ITGo#7kNK!o|2eeQ^#>V|FRSE&tR0E z>(V*t9zN~7lWx|jcHZ$ZzXziI^L~1}AHsPRxi;aKM%qrJx#Rc_b3RXtmULJ83W zkTZ(+aIi9#(yJP+)eVo*o^^ZtitDTGcF*mKvF&DBpEgu;LOfT84@R>s=i&tjZ^d#~ z(w^;x6FYEIlA+Zj7$Q65h#a_PUG9xg<14yxj)d2$b|K1exaYi~#08_;lW*+|7~{Px z43(8~?FO*O3=fKS{r$l{V%G8x2+ldSi;#G5Kxo(b3A&xQjD?3;C&1-}4+dSpn6?#A zt_HP>AqEg#jWPWmAUvmf3D};eSGyFYA_c{9*nPx)J}KdDf9v%Ut0vAc~Jo z?MJf1Pw((y{-vNle^pT$K>uG#`bX}D6K(%D-u2^X$ASU*P`h!tLr+C=+r+5YxIEcWY2MkG;@AU>PkGLck9= zAqEqy*qTG$i@iS+8e=(9LFeczY?RodagzHDrcV6U6Uu_8Rl35euUcJSS(}U|Ke7qV zDyLnoKZNfB5yXy>Kngwya*Z*lrLf96?Ht`KF ze)!Lz7uj+_+hn=yd`bTw#W15c|3wM^XH1hG>Cpcag--JRoVNRa!-7Rv(jz4AF#ei0 zcG0+~P12(RefuxjZJ`oRMc?o}2l9U)hJ7^yF#(N#H^Abb&P#~uCKtSz3Z(CT`i!yj zpCJnOUB2NJ822+>|ELp(uqz5(#dwY$-2cbrDX%Nf&I>E{SJ`>78@%C~{HLvKChpjh zg_lV-lvjHH8YvfsKZ*LkaizPq^6}R5sGOr0df1Hmg5K8<8se{gQH34E3yDu-W+KT- zn4^Z`y*QmgK>=0-6vgqPEl(uys8GOFH#B+kg4yn-#(4`Y#EQJbZ{3P~(P16w^aaF) z6T`S2h;U%ys7d=&{L{e{6uVq@ofmm(@IM4#0mEBw#tusJy*>B{2e4d>WdJtT_@Bsf z3d^uv1t_PT^-_Z5Q@K(6N63FAn!b+Zm|~(5()o|PFj>2X2@MhIO2vQr=95YRH7aR2TT>` z9W3Z&fsGaOnh04$7+}XVPX~UC4ET%`28eggEj0OJb+1r2Ii?xW!*6eER_M0D4*`Ma zLr}*(6LKF2XmyMUuI|WUX8W&fW-(>n&wDPO&RlKxI;-tgvL|0RM^=4R zkM-~jE6LL=KqXPM`EqRs<@W3MJJ|r^tYCM8@}#eOPIU0wSe; zd)cVbizBA27k(CzfH5&A3Bsj}X=RRRlFjLzjY3@LAC;p$0`1)t0vimzXLC60@_GgJ zIOfL7MYtblIfKbr3GNCgKU*o0Wp z@%lrv&8KDThbhT!VoC~n4e*q&i^U0ie6Jvc40AeYBfRRQ5GhH6%|ecbfXjbNTBqq^ z=ulgaR&k9S`kNkYrEE?8u=OsVcR|VfV%YJOLPV;5-Q-{r%VNhe4~d8&Z{q@1%xJE% zh#XRFwMildwqvs601w^l_+yPBH&eOiGbD2NZ-qdZUws$-1Uhy(U%i7-F`Up*9Z_$Mn0_`H%lt21=RpSJ^`g(4kmfHQWq=Q|H+uXs1VX(Yhki-P|;R zl3R10-ynaK%M_kEe=crNd35CW3aqy3zHowx{k3w0O#Oo}6gcB|EAMQUcGy#(c ztX9UK?>o%@@uX!v_Zdks5|u}g~#x|!+9a9IFzI@PcG3fyv^fKC7Ing8_JGIc9V1u=|VLnQ5|~!c}1e1r2OWT zjn?z)A6rzBwZKDe3zCr=F%kDzHpVYMF3@|vASfW)!nBGl#Kv$GOMKfg!I^;q*L;dU z7xI3@z16LoI+5||PMpL1;tI)2>Jd2ll(QMbVe-xDqCx80gsaLz&mi%j(lr~DY19=> zc|X_E!aLmqeGh{hcl-CTwyT7e2ryObyp!)J*hlU2j!dEa7_-$C-r&IBBBZ*7fy88T z3V#L?{gP@=GkJ~W@3ESWP%^Hu@3S~7`Pu}MvhM7|y1*(dZr0rO2#9iiUR?!4wuU4zF&=jNacA0gDXLAJ6NH)sRa;Ih)oXWgM{LRU@ zwMo-If1=^_2y{45mpp{%aX%C!Tf=3d#TKkD1zGo4pRV?zwIUSu;T|~dHt?qd{ zno(NcpCcIE-gCmC?@?sh-k#B)_J#R=p{kAz4-I9u%F|`74b4_Bts!f<3iZ^MB-8G^ zo&F}aPHwu$(WmpaZe7doxmNNJt+Qes)Qw}~qm4#^>hIdqXzL?BkJj!zc`6y_;`sYy z6-SJwp}vRb+D!MPk37XovX%!2-`+mO_DELJUDn0Fze%R0#B^6*P(tv#O|Ps?JC^q? zBHVDx_T4?9X-MV#X*-FLKwJH%o2Aplbyn_&@lQpO$6P{UCyX0$<=dg$ZQT6imlvz2 zDTRd;W-yLaYcWcWlZy;a`tWJk8F|fgZqe<8!%MUNf{XR#l#L<;U(+~qk~TQGHoUbE zXX2Xa)T5)+$D~_Z)GUVR#xSyh$2wPz`%r9KCg(n4Q%tN$!&s0@kT+JPbNFhVLhuRJE#~jK7mW+3}m)tD~ACnr=4CRx^`r>aY(z zDl!!GemF^RULf6YhPbBBuSiamiHFG^xcTyJS?-LZdV9*cG)euWT_ilLbgZ zq@zMb>R%K3GO6}!DCt{wY*>YiPs|I#m*tJFzL8kD4>D)BcFb;~!_l&*>WHqqM3T}< z-F@Y0rmg5Kbi-EGK!D-q(r$ai;s3MOr{momO+!uB_)ZWz`h^*z6LV|BSFZVkd2`X+ z`$YFrDke5At66`qUO2q)*@gGbz8U=;{1BnLK0I0@%8iae}$vU*KW zBV`+8k#c+osnZq_ej%pR8CiO=?GAW(zk6#-eZ*Mkq=c<@6KU@2=}AOG@=4~fVn`8^ z`d8{AL$3@=24Swz&)awB)F9zn4vbD-c+S096gIr+`ecbe$?Ez?%SWpSv$L#3t#$f? zI2W?llc+QGTmxlQw@c_VuC%3H+_#t=zt_QOv!k6tqa=TF46c_CU1thii%R_Aa=3C* zH3-#Izwg!4R<{#TzG+{k1-aI< z%T;NPcA$+m4z+}2T%1Fw&7O91w{IE%Q{|7q+)}bi6bzFw^{hKLNnpyR)c*Y0;qA&e z_XgE3)!jfDlpWUiOX0Rl%D`~qx7u%QgsMRhnTYeSlNDYcFIA4>h!`Ob+Rxw9Eyb4E zzp=n+1bc2h-jQ$X!t79MM6yI19kvKl?nPg5mhl}eMh7ZP%&Up=0x!*(QP~p;jFq~l zPc4=aRf8haX5VIUkuP@MVtLc}38ZQm5{O<7O$v)aWx2xZc5CiktSs3ufE0H`NB#AJ zmiC@lk1v_*ezN7Ye=MS(a?LQ-&z9GMxzE8y4*RY#vlZ%Kuhr@!xjPT$1yfP~1TsDJ z>V0m@aJk5hJd>L$fq);{6Rc(EL*XU`sBF311z{<5JP6Cai1xK=?jhUJlRWlR`>>)4 zFbsY-LJDrtdE5$oNQM_kRDmk?zh&ulUs?D#BtZ)fN{yydffIkJC2Xe5(sh-YpL0_8 z@!Uo-k5fb?%h-lI^K-co(0N+5;$mRCBDCeAa`D0OJd##B!e*N$-pJAFshO&J`vd_L zb?4sPTLon;$F+ykf{8c>KTra<8pg~AR(X~l4U~H_FI0mE0?}JR&Gny-_OO=5U85aL zds^c*+XKfRlNL|3MNMa2jS2-YvP@U@cJZy_Ekwha9|T5Zm60+m?}-8!Kmnq(N%4t> z#0~aP%$aP57Af%TyV31FQcnrL6Mi{H;;i?%)6V67c8hMgfiIX7kf&&1-~v1<8eeRb zeNn|Da$q~sYJmcj4ONjItdhx=Rf?+!7Y*p{VN2pD+U%%yij+lz18R-uuhGzg8+WMf zMraa34rQ*ht4X@KPn+xeVlb(a`sn!eXW;rRSJJ9&j>yp9{!K^p_rc>UaMubNT=w@B^kOaCBkqXAa?zY3_TBaGx&P>{qu~c^@Mv%$H0G;Kr;`(f z*dUPZ{d%sP*wbD_ASyo4Jjvh<9Lxl-%nz)bY=Z)Jg;mAytmPp_o(Glmoh(@6{z2Bvd>R526hAf? z#+!XK(1PE6YA!a2u2-i-m>=T&C!=Nja8>)8(Snrt({Lt~-~FCaO71~T2B1H+{ID2m z)?MNW1ahO?H!MvcBe^fg6ePLF{}8~5a1)u*31`(9+m>{=&=_j|tU{Iq&eODiPqA6`;2VeY*F>$vD z+C&l*eW&~bRA0XIGd;+6AGv9kIw3dZ4EMBLQuDf<6QUokRfj_gV>g73$BLuXL=U-- zAe(j4B?OMl(RLnqE{{L2Ll+r-Tf+y4z6Q(Dnh~8o;+EQ6R0JIs`TX*-vI{z)P3J5* z)&O5=EK`*@Xl55C*ow%-|&N$1W`}12kA*N*sQ>cus5GeF!4^YO<8vqLs5Sv z2{UBujeQ1jQyVofja}lM)|dmY&uAh7j`!6~^H8*{N!aSik|0|DHZ<|C>p;7T%cesT z?rBZW`=VR9;P2{z^cbCl$@F+GHF>|FSW<>EL9BwLHYEvK;9+#fJ>*jKS2RlBxR!3?B$e)!EYA%ReYI-!b|M?VhqwH}s6GqpcN&Iq0F}Y;Z!Gkx zim)Hj&0zspRm4_#Yhaj+%Ld60z{0~Hk*8JQ(vRlz`3draQpnILEOTEJYMQW(0!BwD zF~?|thgq<==Dty97vf5ab!wOZ-Kt+(j^Mh~DE5sUQxO72M=Ja-{1?T)#8@4$-ll|i zGC-yWHqDX^7|#34fmai-n$Prc4>80-b|}_4g%03+-0hG77`OR@#n)(fp9OY0&B|gi z<%s^$Pm>3>XaKutQ31RmKwjTR4k5s4@tL*vh(O2qplluJ7y0d!Gg=G)kV|X*84!uM zRD4q__<*(C#w-D^CJJX}s9CWBcIJ=G^A`rx?SY-vj{<fKfX;(!dvpj43;JQx#{dU6F&?9ZMzasa}DrG1{+0k9vU)IK-Ni-1pNNywT>o5bAM zm+FZ?oHc#&&@ckgwk~7kwa&aqsL=Ns2K+Q;xyO?VSdx%nF$#!VMNi~M$`)uNO4#*> zOfmR2sc%*#O)v3DY0{TwnW_$=RU5G)QGsidN)VY<;{YqZsic}NZ$#ybu#hZ>ga+FA zEH_)SQGFs_F)UC6r);nu7mA=Y6@J;OtBRnLqVzBG4h+C#9?TPd3z}1L2pqt*73GAy z>tCm^Qq><1vL}m}++hBWdOM5t`5pck)HqIb{FOHNLP{Va&{7d38)5Fy^3f4+LSkW{ zGkHA-&oUg?n0hi>%2rIdIR!V!QUT2+Ngcx~&q7Rkp1t*#Ve+FWu)8Vm!2SU*^Ho49}*&g$jDb{pZi#WqZ16o3hoz?edr$9B$|nMy5}Jl{GEgR?FEqsyq$s& z4?CnNfTIAKyC9!I)gpx+uUMx#-F;og-KZCU#BP`b{L4)pV|eF@2$Rk-m@0Cp4xi4F zBa&aVCawmy{jB+VijE%QiS*h&M-j=T<;}iNU>YTiQ|}=AYs*)7r!8+l(J7-2ZH9Pncj6wCxMC-32_B@xw12FqQi81nY&^647-tTAuX{M`bxm62r+jaGeO_oXv04w;iv*@XLQ!;8}{s6(z){{ z`1apAloR`3p|2biJUy(db3lP)o`~c=WYpktbl>{EK&@X;jRHSs3Y9vbW7IQq zA)ZN~#+VIzk|gF4z(LUkYz>ikE~SzQ-O>$gASX|g;MXbzrBpCiZNq)zFAKyW;<|wC zXJ{v~L3?Kp5}n}h*!LGfypDh+ftmz$veW~)Y38-;*ky4QG~mX|oTli~AY)k471DkiQ$W5Ma@VY&SUnV*Q1<@&^ArSSQ8*%51+6K_ek)`9nG&z)W zx=moAhv<+(x0&?q6 z6q=zG?tK5YyKH}hzB&L;ds@DI1p^E)!DD23=_nB%D69v>dEPR2WfN)>9&N069I!%a zY66=gU&&XNuVye=J%fJjk>T5mKvL@IjJc1t2NE*PseUXP7 zWwfBc-gUNmT~{Q4_v&{lL-p)JspwFj_kdoF8(Uq zuV+<@=EC@vAypp88uZ(Zevu0IsJHbIZ`7)Ja3Hf*0qSuj^eR)o^v%VoAU=?2!tZi%KE*?;UMEQwvgUM zr6Bz@LfjHzHb5IsBNxAc(vg=dj{jac`qje2`2x%=Ph<%9uoz}rtJDEe$s&}l(1kc7;qoGgiwrVP_I zCJ9wis0!Y1OX{Amt%trMT~~vE=7P>ifr&~^lQ(e(%cmR@o@BL)=kgu3@tevq?x_IA zrutq0bov;TMX!9zK>)BcwE6Hj?7e>nmrgT$pH&9r(C7c=Q-QAgMWxj zKd&JfLWn zWd0lVmR|xH+&2wneW)77HF?(x?ZiX9Et~lj`fbXV0!(xQaPAtKut^1%F6B1w{FDIV zR`LDvC9w4;P_yB(O!KEeug)qXrrX4DK&)|WiGivRJcwR9mRSP*Hc`#0y1`u%#xC=; zKh(9FOg#8|jcmea_Y%PEeN^WUJRhNrB&)zSTX-&cZ-Rx_&_;sNyy;UK<9WgMi0Pap zzAy|5zT&k_o7q7m<34+eo2kIko7;35QVpzsMio0tc|dBR@8pzl&kxdfbxkaM&k6JP zaEpU;G#>ix;m^rNevWMNal^LgVc$jAU**GCA_tuAhsaG@Ov8OO2=GAc@w(f@YJhFs z+V~Z}r-O>JHrytG3@T&9@-XrD*v-?5W-2C}dER2K<{b~>J$Ip0+byHY0u>FORYlgg z5Sc2zg8@f$oFwpo8#ha_hEsAAt3F40D&c0oM0F^!snWQ^74W^wq56 zu*0=m54qkAdyvN(hduNesn&NN@)#Vq3Cgv}eBzKx6dxKcF=LlOTrPSv3N_ zt#UY>ueCFTCoM>7IoIcU34=i<0QEp^Q68yd=9EzNA`A_pfDO|Y*ljZD1Z)wnLVsM% z&jBtsYyK<;gbPp|J#fh)^F10U##cud0l~hZXy)Pf6;uh_V`oLewkF<6SH%V)Dox)dd*ApHNf&*gJ69RR;fH^|Ld0W7K%Dx|+s;W&Y>UV)!O-c|w?u=jQ)h*(uE z=GL{#ltX0(N;YB?LzJ;33CJ_lY9VEB3pIh-Vyi&#&wE%HJB4%!+>;Il$R8wXlG}U{ zn2M|o;`75MqGJ85jUC{}KVXg4&$pFn0sNh4xJq9zpa~L{Mf~>rD;~@P>H6oST6UsS z_pkyR*L0Al=$W~>^hzb*7Z#|A<}c7rNpa9%dl&%)qEq6K=q$UpxGu!dMd5bWsHen~ zvVI&o4c*B@!W;``U0Gz47nH;LJdnIt)$*`l?T7~)-S*JUEscZh zlL3795k!AEnk&S2KpXAGYiFl_1&&yd)J_XA=J+sO51p6q=|;%CrLsUu<~^5VM7>0R znZqm(sxi{Rg=3pDF%r8VfWo;`3wwFcoG&c@6-qi(+gQ)`Mw>GHLEy&ak2aETz)&|t zsm@#=FnsB$^X({rqT$w#_mQC*+G#7k1k&OG_izyB^7$eX31F`OF~ge>@*5kPw&X@T zdxw55lXfTxn2b)UnFEa%FGc%ZG*n51I(RgmRM-uk17_4SY%VCu6Bb-a<wGDcA~lQJIO+T0C!K$8|2(5(9x61mjU>p0QfmjH<; zUvn>D8YrV6mlKc5a1W6^g%Wx%$57L4olcI4cdieC8K5h=->L@+C)32hLK1n!Ea?twTs>27SB{R<@-hjX;WgklLr?BV@?k za8&8E4(*(AWrvXeom+uW8TXBRel{(HEEelT8A0bW{7LS2}92`7!;#h#*Py3^WX z+}g4$$TeOQoEt|j6nPe(>gYR5QrEY8N>XQ4i@m|7ls}qgKtR8X=J$)RX&@b*<)~rr z)0HAJPSY-o`Zkk({;Pc|3}OnLE(~UmpfrLsV31{=Z`2e4t{yBc2~7@RljBOI1{FMXb$rZnlP)Cv{mwsa=tY(UXE2vlS%iVjSFf=fhUorK2gmApetX&piccpxP_Cz7Y{-G8wJ1_Z0uGYk<(m>*S z96T%SSVG|Wlu=GY(?+kL8@${+BAW)tuCUDPtuZM?a|DJ11@)Jo;6U~TlssdZ8an2g zd@|w$rc4lmUQi33VmozvJ)bm#3$&OdpBRZxt`uM95XjuFa!fH*4(UsG(p)v0rI-)f zE3&-7OsP6QG(=&m5*txn`e989p?@jq z^;3dt5XBf7phqN==`S#;dLJ=^QfH%FLZ;3nGTF@9u7y?8Q1r|`qII_ssxH;PCrWWH znP}-rJT{;w3!Z(nB)wp>ge6WQlaRZMR~e-Z!VW4TRV9#0m4uQaYN=%mlEJ6`V1}KP z^#=LEKL8#!!x!)e=Era%h$`4E*|j zID*GIg2``l4S$0^Ps>t70-@Qs>BQ*LpfB1IM*6j|5AJ57w9BY$4zn?na8XKku0t_9 zuTfegWh4aSVwQuEM*s8CVc_q>%uRK-*Ccdue+EKl#z*fkD;TT( zOm!SdQ-U7;bciS2>E$_Y>00ZMO_z@lYZfq}V86<9i-qcB6j+yJEq#PFPQrn<-DZjP zeZ+V5ltpfm_2Eci(mj0uw+0DLkyXeiKr?J-;=E0M+*do7yU-VBA0?7bV)N9XFe366 zVu}mQ1uekCrnWOb6Bs=sKDJh6xSh+@HTPM z`2JrR4P2KqQ-H{3tgWhaYwakNcaCl zcZXsW>?jCA5Mo=N4)rBsD=hXo{1LqdaIwN6z*IDW0^fBbiKVLkj)IU9j7LI=LN7A~QKqfJ4{=$lIKPoH zc}4pQx{V^orf$A^+3IjMR^QaD4n-{4k;pGak;SPW^_RU%Ld@43Z(EVmd%cgr5%Gl9 zwx!&Jpm(82CUd!N6*#INx_RFH=^E~Z*_iw4`;okJ#7V4Oyp_2PdX;N3h@`?Gth(z$ z9s12D;>Ne8YSEf5`1B-j)+D%JqJ6aG=|Lt70z*~uv6r8fo-}1&fwWP!Lfs$pw@kA< z4o53Y4ifPsK+!Dz4m>o_KDtjXqNEsq-Y6(45xgPhxg|@gZhW}0nnS}HFgWWzp1*xe zyxlk9&PTFo6Evdlg@==rura;{RY^B=2_MGuJfRI?$-HAH5kleQQ8>3l8ws0LPQpfZ z(cs0&kr5;j3N-dGBRC42R2F)xg;*2%#M}4N69;1z&m0?6IuOmd8B84lD=9ct$?52V zg=ZEQTFtJAJrs z#P((lHe(uv>U<0{z;AsK6h)>(PN9+&_yt^jNmJwamJF47Q7hHjZ|U93mq!Anwmm(5 zH~)E)GB>*PfZmDr3O6+zY6PZ})W3=1PlUW-U;-V}lb%VX;QW0A`P}j8{VDV>b3J=S z%DVj4^nQ(|$@NAb+QoL?mmPYHB+1xikj48de$m1B90()N5P+TwXgE9!SM+xdL) z;bu2J3YTTURx6w^=}=i94Ac(7RHxUu4n=HjpQm6BXzK2v(Xn5fl{|EYVj-h`wm|LGl79M>zC<~cvhdntQz z%tVyjmclZX`=ZgGn!)0M@`SO>G~ja6`=34#EJS4alvBAZIt<^ARG~XKp>x_H7@c+= z&C`Y;*(WJq2zx&7R=g-Xxf|ZBSq!z%6zz~C3!(;QCvX)Q9uoC0|V@{87b0S)auMY(pfo5VIZAWbdQ-GKte7$ASIHxae|}!VR0~;_ zrR>Q9A2p#kOB64;-kS-%Avu%z6E9lLo${qCZ6JXTS&{RPH<=79^`?dX;%9_BnTKq{ zTvFMxwLy-(3U0vQuPm)1?xp`SZx&KiV%~Vsdw{+BpyP$ZIe2e#w|fn+?VF2lCLJ`> z`EWv)INQ}-Pz&PM*yMLOo53Rm(w4vjBq~X=w26;2<|SE#u_in;D2VuHa#`P)63~3} z*hjPG+kh8eMPwe(7E(YtU>N6903MhY4&t4nEM!K4G%AjvqI~w$tAy`2Na5ZDrXLa&tlODMKNjGVi$l<}cJGnCVw5 zk3-Ii{Yvev5295rEe1{7k-)k*7EE09pXZ+$NJ;p;_5FvdP6n!6k(hW=V#$a#10w-J zkWZg&U=1Y9el_6`giz8|j0?l(b^dz2A<6FW4QYw%t5Gr^&$rvCySZ}(@>5VFe z1r4!X?(XbC2|*>hi8VnKT!~?A!3WsOGAvx*APw~InAVE%PLr;n1A}kZ-ek{Q=qo7? za)8>}yqIlizmY=K3wo*N0dZvICV{Sa5Q^i21GN3aD=?cD}Hf;jdJM48A znP@?nANehklIZ%{NYnWacnNb%!@-eu9=1`ZB%LCP88YplQ&@#g^_1_ ze%l|e8yKjhQ+IAMLfF3tO0eErSw4OjI3pORy~1H|AaV*}?s3dMmUCIc1-4&pn5nmY ziqtYJ-MtZm3((TUKJeCjvKT!-qr@s1@^$hDsdRpYEqd<|BBy8n1;Z<2Locb?4CCfwV1@FLy7Ln^8M6#9Mng?7XzH z<^*i%&6)tiJ#L)G3|XPRVQyBJ6)>))_sqYT;r-EkRm@9}?z}w1E&KF3v4v*T7@HnK zTU(OOs_eOz1GN`lD4gpVCA(U^w`E&6SR?(+or+SmGV~R zPqj^5xyaUMl$dtkPr8wPR2%Lo#X+$&rEaUA=tk|W4-JX$#}Kd&vCnyh4~ATDj_ltU zL9>h*Zj~I#LNAx-W89oSrW`A@pR`e^9lYNaZg1i7#HkYW66w&NbH8hJL-zS-h`qep z$ci-+RX8?m=!tY3txca3NOZSkUQi23J@JJ$vwR{-nhJ6a+-nP3Q~cqUS*X{D*))j@ z)7M_w08>c$Y@q>)4ZAewdAAaKe?<^dQ;?ftXt+$h0`GcrJo?G^W)-!_(;9}y3GdFH z>RNjR?IIyaB7^=D?Xw(C3pr2MRaK>9i$7vo++1A}^#|!Tk7Nbf?0G*viY7*tDlKmj zCY_ddyV7s`f!BxyOqBmJTh%?W6m8J>0N$d2IMQ{E@jpM658Yw9?8|iBX+{B0)}x|2 zdQ&bfK;U6W=9gPB?p^V3M38#A?!XyuvQ=-KMvnE{&rBt=1Vvo%d4Y!rM!owJf})1k zN-7TFcl)Mj__8t{CSeJEm_4j(v2)v{NU;K6@Esrw@kE^fCtfhWv(WJURTc-(NAh$}_82g|@?z zG~3hZ>hb}5a!l+#ZjA{fO0|B46Ug2$)S4ry(!xIcWTg3omN;bqR@}eB9uj(3g1))o zJ3#hy?o&7sZTh&8v%RViT*TSZ`0jdRS&*@9=_tT|tuIaC@0nre_&Ld9S;Lay$!*wj~5^GWo!n4e_GJ_htP;rNFNe$tVYdrxR>;aFgWf~XPcn3cpn)!T4y9;=N%c@c)fMq;hy*|637#OZ17gCJsk z`l0HcghNBhvs$u|0-6%lT_@S==7@<>tO!fEgq{m|qyiqP4!HCWPJ z{M!@K2HfXl_L%pIKl1EoO+j3^jMxqLI%xJNjK+Txx%U*lTVVY*-Z52D7rI1`z}e^lXx&GLR17<{fRr1EbUrl?Z57Xtw>L*IQ8 zg3JNu0wDB#IR9;C8GQV!6-mvj0#CQBf4ls^SFC>tp*4VJRQ^xte;kLOACRtu`{L1; zcKby6{|@E(f&Xu_VE)C6hA%=DYCWp|MO@%DWP!Xy;cFCii?3M!_O3aW%J}OkfEWKO zM`ALQ|LCoda(P)C1#lR{e&M|T{fXwl?myRtN<3Kqqpx;>fkDRcKgi3_&oa__^nazl zM09PHxogH~TBU+rHMQRpoiwxI$g}CqY^e^$Mdy>dw=<(o#m}$9BFuHq&-+IkCc`&g z)b^00bq{8+6HI@Hv^Q^u^c?gNkBh;$dSmMua|^ynW3c&JZY#-Ju06<$1TcQf|MTXw zKgmz*T-bWof1w>WA%{u~C+dJ-S-1~pme5iNe#S2WMRyhY_`c||TZ$GMbvSFJUNcy}(b zk@XM8A?MTX9O&bp((Pvtvgb-2d{?K38E5_ZB?JfbFMWk+_WqW{r8KktGxOf27zKYe z7h}2$8Lh-Clual9$fj!9QUoY&K%2eQouTN$%-gfT(X>6Gy80GO*{K%r*MPsM)lKZMF0t%WB6zhSlc8 zi)7lnn&u!G_qDy*x<)Cm#ASG0_`{rTNc;!sUxH*fTKNRBD^n;I7Fo=%IGpY~dhCwJ zIFB&dQnc1EZ>u5a*<3d(^RBjW{V_>3^w~?$!PhG`77ALV|;SED+L&v(N#rsYCCTQmI zxmi!#+d3k3K5C7l^c8ZORyv^3x4Specb9e@TujT5eN>)oFix}YUhU&LFmE9E&UIk< zou`Crl*F^1``29)gF#|NLnTe6d?!+;0&kf9b4703%4DF;oFfHo_4B=SlfIjQ++g*3 zqwDi&RFiYluj6RJFxFoGPvgDOJ^QZg--s(wP6KzhbT3#i1d0wV)>Kgi3Zr`(_A^bR z>LllZ?teb1O^GhkSym*~tfja!Ro(YFu3fimu6Q|Cn&Z^|{s_U?uRZ@@y2|}@z$db3|9$JtZZAKPO_6?D&-5UR7#W zl_{D^-fL3Ah$2XfKMNUouGpi=#5Bw-w-W44`=QsybJ6))B-SZPWz<9Y!MkTrQ12<` zEUQ`e;hyQ)cr%E$U!UXIYOtWr*)6cpSy4N!HE~<-iT@1Nh;S`+`y~D$?4HK7FiXxU z|6r?z>BHfr;55Bq;x$@}4#)GyL6L{IHTs43g^klemwimnUy}BFJ=~L@FShiI zvXn+2K4ct^TNjjbJUab>dY|ybKP(D9lISNIyDZq3=-_!Ta$%fxy)|mPtCZ!KnT@s6 zHS!_VR^0fTA!mE2Z>%>j3g`2o(ua=T%b`mv&a}^&BM-6@<9i%-srv{c)(=g-q8H8z z{bwS-M5jOG>(~ehr`_S`OulXVP}n*xaIDP8@!Y@9E0pnepstR_b4!QwxwVNx86r`* zmK)vnZPxk;;S4SSHY<65|ABsV{f6T?rVCSmjgN9V>W;%`sE70Usf(DHbZhfw1VTej zzO{+7`B+Ow(b=;>ZKatWEBj?!X6&}x=Uy=(mf%YkO!`y5&$q0d$A~B4$$g5;IUO2j z=elRSq(+Lx+K6d#o2+oYP7woV%eZmTk$$tJaoUkeS62~HHseKsO|s1o+6l47Uv`7X ze16^agX1vb5(zwyPqyL1n^=VTfEyro%! z5r>wi7k70Z=W#%AVg*eTEP;NACpd^}DvHxM&qZpE2f zWNX{Lrn0;qvw*8@+dic8FsDgk&uH5&rr&abZ!{fqm#%&08DQnM&G3_qm~k43xeG@I%yc?! zGY*y#)Ue#Gy2oiaw%6+-xxpb^=;WYk<~nH4b3X zdvMilo7XU-P2k&E^95dQPt1O}%bBNt%kH36KMH3qId<#CTgL&R7T(Lp+2f)fLa{L8 zhq6J=+12}*@z*xv+!h*#p({x}{JeyRI610W{t^C+H>E?taVZOdS7NEbtLUA_@wby} zIsBZ?_=j_9A2kCGlh=h}HV>UgbAj~{5>ePvpbNhl`Wua5Y?K1`O zY2(zhd(}d2ed62g@^+_j998b91Yyhn7x+9mcUE z*A{cZ;=fQb)+07py)JsI1fsT)pYOQxP}Qka+^sL&OL>d;ozy(#O9dr)FGm~XN^98k zXSnJ=(!W7?20tox3iyba{j%iYp5=6+Z;iSecaHnk!p%4QbY9^6Nib=0a+JindPik~ zGNU@B(O%*x;ZW+Df1=R)`Bmg>esZgf)g~~?VuAGhZDBX|lsuyZ#8e`98RM){x^gLW zHu=KZ5!|$`zfXCb7f%Te#Ww7L{K663fVf-S`_p5?+QlpAO#c zON6_N<+FV$x?pO*hu_Ceehi-s3xL!%%n>;|`xautxY7vxl-vM)oGi>Fo@s{ zJ!|y_-f1?bDV)gmS^UIt0kL1)t)o}Rb(_gBhM)%h7-*NqQ8j0{m~Fc_gEPQx-`n&^ zzqTFE`hI9zb&p*<9DH(^wv0N`E}DczXV1=r3qp;C+@J&;3vu^Rz)ALnW%uH%pH0t(3IQ| zhT==WssaQUUgirlNSpc`$k%rK3=I~f$iXWYpw^WZx@kLkptjvsA@#X7saKI5A zTbTcm1Vtb;xJ{W23RIJ?&Hz$K>EKm-@$`g(24f8}EAmgvQs#S_J${A+8yTNMfE45x zm=!x{Isgwr%lW~9NZ8*f@>^@G6`cp$r+2@)*0--r#)*?mPXe~oGh3^$lB!>FoXIYu3| zP4K~iGD6uD`2%ka^F8qx0l+}SQRRE$Hvy=~`&KK~SYQTJ;xBKM?@9aFH2?9PZ>s-M znL92dm@#neUAaISF(4Gh2jF6$Ly_|CwYEr%3sgw&$?iPNs9F~ zRGV1F7(gT}b{eXF#@Il3VDSqH-HaZf0I-RzA&+mA@TPks0fE52xJjs+Y&CF-Lq zcJSbR5xRl3(1!w%$X329*GqE+VCZIQ2L+;|bSIC&0KG*qLdsu1%PU2%=lH_!iwZM*4Rxi61zg#Xc*BNI&Bt$Z$Nek)IgkdTYm zBlTbW+5sbE>Ioo*f5IM0(<{pf(4Wkf6>_oT$@D)$pNsQ9;+~80KMHCg52)&B0Ti#A zxgo(}a53-7iHRt%!Gd3G@XF6Ho4(~gI<~mMf~-txkX7B}ED_2(>6RpgT(Z5zNv-@^ z(g4WH;b17*jO##w=qDJUKz7V3@5)gQ1)wyaX5FDcehNkioOt0 zF(x(;nosbeK%==xIm;-{UJR@ZBd5--4#y)`4V0L(FJF+Dy}8E8RvUK*^$NH zz}o@s+W1WmIDK#f2y&@sZXn(%&RGC{xdcLgEGZ4>8lz9B@O^tU6Bjrhqlzg6-WxBP5S zpH=ZizdbtO=*t;Vz!7l|6dZ^b762P6^1PPn)Gj9@Nc~D38;k?z12}LTkr7n0lY$M# zyr#&1B)P5=yOFO`hXf5d!9s$e1BU?AV6_7XH_x=SWCA$qqt8-drmPbb$ShME2tfm4a=^269~3({;{eC|kIUtI zveW=)`_9#3H}}qNP@29i(2yXBj0hOjh4cv+RfUo(ymCEh&cA*ix5;nySq=sWbLJQT z{KN||KUsEC=0BoEdiYOP4cXGHVtX#x-$QPN5s)D|~1$&Y)P-(a({0P#}0pAn;z9HHC$4NM^S2%5xK9 zfmkNJdJhezTDMl>XA=N80P6yn!rnME0RmhcEp1aC9--qvXUqpue$0H@=Sj0(A6<^jusm=Sh3P?=Z_hN6$FHySi}d#eBlF@;h9kUpvd zLM{YL+7RHRZ6NOAVo-pg7H1C!hHys@zucEMvs>r}=lQ3YOMU)eB3O_s+y7|eLJ%P2 z9V!^rn(YF>Z6C(T@*jC0*9%+un}CV4;R}7fCsvn?&<(M$tk8{S4-ne=asb`FU}C*9 zevw!JdYIh>kOt)6g+}AE6&y$bWsgmfNEuiORQ{bIHYmK*^B?t9fz<`?SvP6dDgBEF7^z(i8Neixcgz5_uUU^gQuC+yA5k7^K!LPJW3hKmXOjYsa3;8s zRL{St0JYlGbr*Vmt-}bY>Y7OahK|)IUT9rFAANXbe;-Me#Nv}#zGwU2bx6#Ga@0{! zk`IFMOUiSu|It*UC6tF3_`~qZF%rOfWqtschCIXz&--2QMIDW80ka$8_IrTClb+t~ zoHm6Et^2mI4zHXm!-2v|Q2}B$Z%q}R@0y0k23r^d(T<5q5r1R%3`8TrRu{(bijyFm zhu3;9sofMnKyPHrLH&Edn3JuBrM-=bz zb{2Sn(QR?~9Q5p|4lF{P8gPQzU(Eni3;Mtw zjvfE&)j}NbSf?82kOvj@Yp>{+S%S!yS=#~V*}f)En+DFn6W%G6naC}p!-9dZS)(Y_ zlCIRZzAtff@=`UlM|aZukyBR|cUwpmH?x-{9_(*A7uuw+h?E;2N4gY8HA#2L;(&X0 z6v`H4UI~h`kE4c*9u}fDEHCsa9|Y2#D~q$i%Ilu{&$ah+qaIyJS9Zy7$JtjyTVeZM z)@GqtO`JewLy-av;e45XIrrAdSH9HyH7Rd^mA3TAt*QR5oShYg%cdkpU#eXfmWKya ztUQ5Iui9_L3p;|B=C7gyHC^|p$=(t&uK~*Pezd3_?1G~_zjT-$?BX4(nLbX<=Z5Vn zusMEzU^q3EW-H6^7(}8GFo~s;XMv7VNH2=Idw-W@6b$q_Q#duESJc%0!oeVsP>@U( zy)<%acIzpcS@&`(-ceC6uXRQ{J)%_IA@ESwyPJ@L@|N~hZ25#~&wf*Klg8_(RIgDl zxpHq`B8QT6y7?nw*oY=->b1atM95Ha^iWCFNN-Z_Y%e=)msYpO@?4G!4qHz{65CJu zbu*rK@Zng_i8HrpGd%B!iu-SswRpL`cw2H^#dh%Y)I~x(mK##%<_&Gg_if0zmnF}B zDv$ckV{n(gS3sDOl$tH>KU8*X%r04W#-^kEu6(3H>GD012WQ6`mfK2rlulBU-zG=t z&e9_8(6M8-SnijnIj}b-lEr2kgCNsn8n&-eI{hiTgtb$@U98T;mE-#U?(>use{Ra- zM55;;CaC0W7Di|2=i|In{2Jx&5q6Pse1#S?)(&P*t`kf?Lfguv%X9Vbzcd@M#p?US zlvk|&0ba+N=T548&*|;aClvdXCNBVr9VyjsL-Po_jc{e^!9laG26+wpUu)!u+t`mL zrFlbUXF&Fr%S_pcC<$+6;Q~zADS96)=X~V6^`v-1uV!vHul|9b)z;n5`!4#2^`z%> zX>L0i4*)7=B}J4QxFdco$!y}*MfJ~u;Tu#J8>A1N3C{o~<&@V^Cm-CHb9%)~jnYRS ztahJ)GH%Ril6HAGXQuM#q3>S4sD}VDzxXTgsD}!-A30#>;lV3XANAs#?x9uswOUyo zmA0mAWXiAM9x48c#B@2#=N|wT4cPs-&m(=1PkyGBDS67A8MiRhV+#wL(s&F3?}gik9+FIA=VI(3G9lJTK8n;beGt6o ztQ_kI$}rFho|Ub%Pi5A2pKL(VN86L;u`ZkQaOJp?bCR+lo@5b3&cn%N^OCOa_`VR; zOP~0pHY+|k`*m_w{(#gOlP)Cim&}PzNKhZ_9$LkULE^bRZH;OfC*>()ZHLWcuuQ*O z#35cMIsbKR1sVz_&->F5*G)t2DZ^PUZ*2s|zJ-!cHPUN(*osr2R;_c3QoQ#hKZ7^P zd$&uKxJImTrkYreschFqMu0g&ZoAYwCu$I*p=#RGluY)DDlbaHVxk`=`95T zV4a$QpY5fMJ1Y{GP05b8371;b0C<$FYyrV`4>6z$eB1RVnQ(H3P z5j;@%Po8)McKHaRMpCOnb_hCOd?#OCCSUlQ?oyv5F7#33%T&~lrU+lxJr8>0x%+e0 zMI#g_j4Ik|@nuPCvWCWw`L zqU^bM{azmr?QT>h{vKI!HpAT&D2S%nh|td?ip#dXv{M7W$LPuZJ;;@DeO$+o5}Z-z z{*U7*`bESSSQ5)3K{C1kNl)>&KJ<%R$;VECq;)ha!A{@d2Chc!HZ8;5hq^v|*K=Ih^% zS%j?b{4LcXUgPvv4f}xjAMuyFf3(bwZ~hun&gewD*!F?df8`bb+pa^0dp-Q8tFHB+ za}+;59<*2I>x4fKNh9fttM#!0K6AH2V| zD`fSuXnmIK3z*h$4HYWm#1Q|5)V6ITPT9`IryXp>}jr|hZkZok`iuI

^+;A}H)%BvLa8pEHN^&J7n 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 K<$(JdApZv!jW)6X literal 0 HcmV?d00001 diff --git a/tools/iso-styles.lua b/tools/iso-styles.lua new file mode 100644 index 0000000..bd70d14 --- /dev/null +++ b/tools/iso-styles.lua @@ -0,0 +1,82 @@ +--[[ + ISO front/back-matter formatting for the pandoc -> Word build. + + 1. Title block. mmark-to-pandoc.py carries the mmark title across as pandoc + metadata; here we render it as a centred title (plus a status subtitle) at + the very top. Without this, pandoc + the ISO reference document produce a + Word document with no title at all. + + 2. Foreword / Introduction / Bibliography. These are ISO headings that must NOT + be auto-numbered, so they use the template's ForewordTitle / IntroTitle / + BiblioTitle paragraph styles instead of Heading1 (so the first real + Heading1, Scope, still numbers as clause 1). Some docx viewers apply a + custom style's paragraph properties but not its run properties, so the text + falls back to plain body formatting. To render correctly in every viewer + (not just Microsoft Word) we also set bold + size (14 pt) directly on the + run, in addition to referencing the ISO style. +]]-- + +local title_style = { + ["Foreword"] = "ForewordTitle", + ["Introduction"] = "IntroTitle", + ["Bibliography"] = "BiblioTitle", +} +local HEADING_SIZE = "28" -- half-points (14 pt) + +local function xml_escape(s) + return (s:gsub("&", "&"):gsub("<", "<"):gsub(">", ">")) +end + +-- Unnumbered ISO heading: ISO paragraph style + explicit bold/size on the run. +local function heading_block(text, style) + local xml = table.concat({ + '', + '', + '', + '', xml_escape(text), '', + }) + return pandoc.RawBlock("openxml", xml) +end + +-- A centred paragraph (used for the title and status subtitle). +local function centred_block(text, size, bold, after) + local xml = table.concat({ + '', + '', + '', bold and "" or "", + '', + '', xml_escape(text), '', + }) + return pandoc.RawBlock("openxml", xml) +end + +function Header(el) + if el.level == 1 then + local text = pandoc.utils.stringify(el) + local style = title_style[text] + if style then + return heading_block(text, style) + end + end + return nil +end + +function Pandoc(doc) + local meta_title = doc.meta.title + if meta_title then + local full = pandoc.utils.stringify(meta_title) + doc.meta.title = nil -- suppress pandoc's own (unstyled) title block + -- Split "Title - Status" into title + subtitle on the first " - ". + local main, sub = full:match("^(.-)%s+%-%s+(.+)$") + if not main then main = full end + local head = { centred_block(main, "36", true, "120") } -- 18 pt bold + if sub then + head[#head + 1] = centred_block(sub, "26", false, "360") -- 13 pt + end + for _, b in ipairs(doc.blocks) do + head[#head + 1] = b + end + doc.blocks = head + end + return doc +end diff --git a/tools/make-iso-reference.py b/tools/make-iso-reference.py new file mode 100644 index 0000000..f3e1d4c --- /dev/null +++ b/tools/make-iso-reference.py @@ -0,0 +1,154 @@ +#!/usr/bin/env python3 +"""Derive a clean pandoc reference document from the ISO Word template. + +Pandoc's ``--reference-doc`` needs a ``.docx`` whose *styles* (Heading1..6, +ForewordTitle, TermNum, Terms, Definition, ANNEX, BiblioTitle, Note, ...) and +page/section setup are used to render the generated document. We reuse the ISO +Word template's styles and layout so the exported document looks like an ISO +deliverable, but we deliberately do NOT carry over any ISO copyright / IPR +boilerplate or branding: the document is not an ISO deliverable yet. + +This script therefore: + 1. flips the main-document content type from *template* (.dotx) to *document* + (.docx) so the result is a normal Word document; + 2. neutralises the ISO copyright notice and document-number placeholders that + live in the running headers/footers; and + 3. drops the ISO logo images and the ISO document metadata (docProps and + customXml parts) so no ISO branding or classification travels with the + exported file; and + 4. un-hides the styles (removes ) so every docx viewer applies + the ISO heading styles instead of falling back to plain body text. + +Everything else (styles, numbering, theme, fonts, headers/footers) is copied +through so the document keeps the ISO look-and-feel. + +Usage: + python3 tools/make-iso-reference.py [INPUT.dotx] [OUTPUT.docx] + +Defaults: template/Word_template_for_ISO_standards.dotx -> build/iso-reference.docx +""" +from __future__ import annotations + +import re +import sys +import zipfile +from pathlib import Path + +# --- Neutralise ISO branding in running headers/footers ---------------------- +# The ISO template ships placeholder branding in the running headers/footers: +# footers: "© ISO #### – All rights reserved" (with NBSPs and an en-dash) +# headers: "ISO #####-#:####(X)" (document-number placeholder) +# We replace the text of any run () that carries this ISO branding with a +# neutral, non-IPR marker, leaving page-number fields (separate runs) intact. +# Matching by content (not exact whitespace) keeps this robust to the template's +# non-breaking spaces and dash characters. +DRAFT_LABEL = "Working Group Draft" +_RUN_TEXT = re.compile(r"(]*>)([^<]*)()", re.S) + + +def _neutralise_runs(xml: str) -> str: + def repl(m: "re.Match[str]") -> str: + inner = m.group(2) + if "####" in inner or "All rights reserved" in inner or "© ISO" in inner: + return m.group(1) + DRAFT_LABEL + m.group(3) + return m.group(0) + + return _RUN_TEXT.sub(repl, xml) + + +# --- Main-document content-type override: template -> document ---------------- +CT_TEMPLATE = ( + "application/vnd.openxmlformats-officedocument." + "wordprocessingml.template.main+xml" +) +CT_DOCUMENT = ( + "application/vnd.openxmlformats-officedocument." + "wordprocessingml.document.main+xml" +) + +# --- Parts to drop entirely (ISO logos and ISO document metadata) ------------ +# The template's only images are the ISO logo/branding, referenced solely by the +# title page (which pandoc discards). customXml holds ISO metadata bindings and +# docProps/{app,custom}.xml hold ISO company/classification metadata. None are +# needed to style the document, so we drop them. +DROP_PREFIXES = ("word/media/", "customXml/") +DROP_EXACT = {"docProps/app.xml", "docProps/custom.xml"} + + +def _is_dropped(name: str) -> bool: + return name in DROP_EXACT or name.startswith(DROP_PREFIXES) + + +# Substrings that identify a relationship/content-type entry pointing at a +# dropped part, so we can keep [Content_Types].xml and the *.rels files +# internally consistent after the drop. +_DANGLING = ("media/", "customXml/", "docProps/app.xml", "docProps/custom.xml") +_XML_ELEMENT = re.compile(r"<(Relationship|Override|Default)\b[^>]*/>") + + +def _strip_dangling(xml: str) -> str: + def repl(m: "re.Match[str]") -> str: + el = m.group(0) + if any(ref in el for ref in _DANGLING): + return "" + return el + + return _XML_ELEMENT.sub(repl, xml) + + +# The ISO template hides most of its styles from the Word gallery +# ( / ). Several docx viewers (Preview/Quick +# Look, Pages, Google Docs, some LibreOffice paths) skip the *formatting* of +# semi-hidden styles and fall back to Normal, so the unnumbered headings +# (Foreword/Introduction, which use the ForewordTitle/IntroTitle styles) render +# as plain body text. We remove those flags so every viewer applies the ISO +# styles — and so editors can see them in the Word styles pane. +_HIDE_FLAGS = re.compile(r"]*/>") + + +def _activate_styles(xml: str) -> str: + return _HIDE_FLAGS.sub("", xml) + + +def transform(name: str, data: bytes) -> bytes: + if name == "[Content_Types].xml": + text = data.decode("utf-8").replace(CT_TEMPLATE, CT_DOCUMENT) + return _strip_dangling(text).encode("utf-8") + if name.endswith(".rels"): + return _strip_dangling(data.decode("utf-8")).encode("utf-8") + if name == "word/styles.xml": + return _activate_styles(data.decode("utf-8")).encode("utf-8") + if name.startswith(("word/header", "word/footer")) and name.endswith(".xml"): + return _neutralise_runs(data.decode("utf-8")).encode("utf-8") + return data + + +def main() -> int: + root = Path(__file__).resolve().parent.parent + src = Path(sys.argv[1]) if len(sys.argv) > 1 else root / "template" / "Word_template_for_ISO_standards.dotx" + dst = Path(sys.argv[2]) if len(sys.argv) > 2 else root / "build" / "iso-reference.docx" + + if not src.is_file(): + print(f"error: template not found: {src}", file=sys.stderr) + return 1 + + dst.parent.mkdir(parents=True, exist_ok=True) + with zipfile.ZipFile(src) as zin, zipfile.ZipFile( + dst, "w", zipfile.ZIP_DEFLATED + ) as zout: + for item in zin.infolist(): + if _is_dropped(item.filename): + continue + data = transform(item.filename, zin.read(item.filename)) + zout.writestr(item, data) + + try: + shown = dst.relative_to(root) + except ValueError: + shown = dst + print(f"wrote {shown}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tools/mmark-to-pandoc.py b/tools/mmark-to-pandoc.py new file mode 100644 index 0000000..3501896 --- /dev/null +++ b/tools/mmark-to-pandoc.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python3 +"""Strip mmark-only syntax so the spec source can be fed to pandoc. + +The canonical spec source is authored in mmark (so ``markdown2rfc`` can render +the HTML editor's draft). mmark adds a few constructs that pandoc does not +understand; this filter removes them so the *same* source can also be converted +to the ISO Word document by pandoc: + + * the ``%%% ... %%%`` TOML front matter block (mmark document metadata) — the + ``title`` is kept and re-emitted as a pandoc YAML metadata block so the Word + document still has a title; + * the ``.# Abstract`` section (an RFC/mmark concept; ISO documents have no + abstract, so the whole abstract block is dropped); + * the ``{frontmatter}`` / ``{mainmatter}`` / ``{backmatter}`` part markers; + * defensive clean-up of kramdown-style ``{: ...}`` attribute lists. + +Everything else (headings, paragraphs, lists, tables, definition lists, notes) +is common Markdown and is passed through unchanged. Reads stdin, writes stdout. +""" +from __future__ import annotations + +import json +import re +import sys + + +def strip(text: str) -> str: + lines = text.splitlines() + out: list[str] = [] + + i = 0 + n = len(lines) + + # 1. Drop a leading %%% ... %%% TOML front matter block, but keep the title + # so it can be re-emitted as pandoc metadata (mmark carried the title in + # the front matter; the ISO Word document needs one too). + title = None + if i < n and lines[i].strip() == "%%%": + i += 1 + while i < n and lines[i].strip() != "%%%": + m = re.match(r'\s*title\s*=\s*"(.*)"\s*$', lines[i]) + if m: + title = m.group(1) + i += 1 + i += 1 # skip closing %%% + + part_marker = re.compile(r"^\{(frontmatter|mainmatter|backmatter)\}\s*$") + abstract_start = re.compile(r"^\.#\s+Abstract\b") + ial_line = re.compile(r"^\{:.*\}\s*$") # kramdown inline attribute list + + while i < n: + line = lines[i] + + # 2. Drop the abstract block: from ".# Abstract" up to the next part + # marker or top-level heading (exclusive). + if abstract_start.match(line): + i += 1 + while i < n and not ( + part_marker.match(lines[i]) or lines[i].startswith("# ") + ): + i += 1 + continue + + # 3. Drop mmark part markers entirely. + if part_marker.match(line): + i += 1 + continue + + # 4. Drop stray kramdown attribute-list lines. + if ial_line.match(line): + i += 1 + continue + + # Defensive: turn any other ".# Heading" into a normal heading. + line = re.sub(r"^\.#", "#", line) + + out.append(line) + i += 1 + + # Collapse leading blank lines produced by the removals. + while out and out[0].strip() == "": + out.pop(0) + + body = "\n".join(out) + "\n" + + # Re-emit the title as a pandoc YAML metadata block. The Lua filter turns it + # into the document's title block; here we just carry it across. + if title: + body = "---\ntitle: " + json.dumps(title) + "\n---\n\n" + body + return body + + +if __name__ == "__main__": + sys.stdout.write(strip(sys.stdin.read())) From d100b97cbbcc46f48c02d3ef755a51997704384a Mon Sep 17 00:00:00 2001 From: Oliver Terbu Date: Tue, 7 Jul 2026 08:19:40 +0200 Subject: [PATCH 02/13] chore(draft): address build-pipeline review feedback - mmark-to-pandoc.py: parse TOML with tomllib, fence-aware filtering, abstract drop stops at any heading, title/status as separate fields (#3,4,6,7) - add golden-output test for the converter (tests/) - commit template/iso-reference.docx; drop generator from build path (#5) - make-iso-reference.py: drop word/embeddings/, strip dangling drawing refs (#8) - Makefile: clean before build, PYTHON override, pin markdown2rfc by digest (#9,13) - build.yml: pages concurrency group, pinned pandoc .deb, pin Python 3.12 (#10,11) - draft: unnumbered Foreword/Introduction so Scope=clause 1 in both outputs (#2); add seriesInfo stream (#9); relabel Working Group Draft -> Editor's Copy (#12) --- .github/workflows/build.yml | 37 +++- .gitignore | 4 + Makefile | 40 ++-- README.md | 7 +- ...tal-credentials-harmonized-presentation.md | 16 +- template/iso-reference.docx | Bin 0 -> 43141 bytes tests/fixtures/sample.expected.md | 35 ++++ tests/fixtures/sample.md | 49 +++++ tests/test_mmark_to_pandoc.py | 57 ++++++ tools/iso-styles.lua | 16 +- tools/make-iso-reference.py | 57 ++++-- tools/mmark-to-pandoc.py | 171 ++++++++++++------ 12 files changed, 382 insertions(+), 107 deletions(-) create mode 100644 template/iso-reference.docx create mode 100644 tests/fixtures/sample.expected.md create mode 100644 tests/fixtures/sample.md create mode 100644 tests/test_mmark_to_pandoc.py diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b7118fe..a7d4644 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -25,22 +25,36 @@ env: jobs: build: - name: Build HTML Working Group Draft and ISO Word document + name: Build HTML Editor's Copy and ISO Word document runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - # HTML Working Group Draft (markdown2rfc / mmark), same tooling as other - # OpenID DCP specifications; published to GitHub Pages below. - - name: Render HTML Working Group Draft + # HTML Editor's Copy (markdown2rfc / mmark), same tooling as other OpenID + # DCP specifications; published to GitHub Pages below. + - name: Render HTML Editor's Copy run: make html + # Pin the interpreter: the mmark->pandoc converter needs tomllib (3.11+). + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Test the mmark->pandoc converter + run: python3 tests/test_mmark_to_pandoc.py + # ISO-styled Word document (pandoc), for sharing the draft with ISO. # Delivered as a workflow artifact only; not published to the site. + # Pinned .deb release so the .docx rendering is reproducible (and faster + # than `apt-get update && install`, which pulls whatever the runner has). - name: Install pandoc + env: + PANDOC_VERSION: '3.8.3' run: | - sudo apt-get update - sudo apt-get install -y --no-install-recommends pandoc + curl -fsSL -o /tmp/pandoc.deb \ + "https://github.com/jgm/pandoc/releases/download/${PANDOC_VERSION}/pandoc-${PANDOC_VERSION}-1-amd64.deb" + sudo dpkg -i /tmp/pandoc.deb - name: Render ISO Word document run: make docx @@ -49,8 +63,8 @@ jobs: run: | mkdir -p _site cp build/*.html _site/ - # Redirect the bare Pages URL to the Working Group Draft (avoids a root 404). - printf '\n\n' \ + # Redirect the bare Pages URL to the Editor's Copy (avoids a root 404). + printf '\n\n' \ "${{ env.DOC }}" > _site/index.html - name: Upload site artifact @@ -66,10 +80,15 @@ jobs: path: build/${{ env.DOC }}.docx publish-to-pages: - name: Publish Working Group Draft to GitHub Pages + name: Publish Editor's Copy to GitHub Pages if: github.ref == 'refs/heads/main' needs: build runs-on: ubuntu-latest + # Serialise Pages deployments so two quick merges to main can't race (one + # failing with "a deployment is already in progress", or publishing stale). + concurrency: + group: "pages" + cancel-in-progress: false permissions: pages: write id-token: write diff --git a/.gitignore b/.gitignore index 723cc0a..3212d24 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,10 @@ Gemfile.lock package-lock.json /lib/ +# Python bytecode caches (tools/ and tests/) +__pycache__/ +*.py[cod] + # Editor / OS files *~ *.swp diff --git a/Makefile b/Makefile index 959278e..2a60610 100644 --- a/Makefile +++ b/Makefile @@ -1,43 +1,59 @@ # Build the DCHP specification. # -# make html - render the HTML Working Group Draft with markdown2rfc (Docker) +# make html - render the HTML Editor's Copy with markdown2rfc (Docker) # make docx - render the ISO-styled Word document with pandoc # make all - build both # make clean - remove the build/ directory # # Source scripts live in tools/; everything generated goes to build/ (which is -# git-ignored). `make html` needs Docker; `make docx` needs pandoc and python3. +# git-ignored). `make html` needs Docker; `make docx` needs pandoc and python3 +# (3.11+, for the tomllib-based converter). Override the interpreter with e.g. +# `make docx PYTHON=python3.13` on hosts whose default python3 is older. SRCDIR := draft DOC := digital-credentials-harmonized-presentation SRC := $(SRCDIR)/$(DOC).md -TEMPLATE := template/Word_template_for_ISO_standards.dotx TOOLS := tools BUILD := build +PYTHON ?= python3 + +# Pinned by digest so the HTML rendering is reproducible: an untagged/:latest +# image could silently change the output between identical commits. Regenerate +# the digest with `docker inspect --format '{{index .RepoDigests 0}}' `. +MD2RFC_IMAGE := danielfett/markdown2rfc@sha256:7b4412559d6ba5db45a14174a28da5b240512e7c2a886a5e4adb44e5e67f34ca + +# Reference document with the ISO styles/layout, committed to the repo (derived +# once from the ISO template by tools/make-iso-reference.py; not regenerated per +# build). +REFDOC := template/iso-reference.docx + +HTML_OUT := $(BUILD)/$(DOC)-editors-copy.html .PHONY: all html docx clean all: html docx -## HTML Working Group Draft (markdown2rfc / mmark) -> build/ +## HTML Editor's Copy (markdown2rfc / mmark) -> build/ html: $(SRC) - docker run --rm -v "$(CURDIR)/$(SRCDIR):/data" danielfett/markdown2rfc $(DOC).md + # Clean stale intermediates first so the copy below is unambiguous even if a + # previous run was interrupted. + rm -f $(SRCDIR)/$(DOC)*.html $(SRCDIR)/$(DOC)*.xml $(SRCDIR)/$(DOC)*.txt + docker run --rm -v "$(CURDIR)/$(SRCDIR):/data" $(MD2RFC_IMAGE) $(DOC).md mkdir -p $(BUILD) - cp $(SRCDIR)/$(DOC)*.html $(BUILD)/$(DOC)-wg-draft.html + cp $(SRCDIR)/$(DOC).html $(HTML_OUT) rm -f $(SRCDIR)/$(DOC)*.html $(SRCDIR)/$(DOC)*.xml $(SRCDIR)/$(DOC)*.txt - @echo "HTML Working Group Draft -> $(BUILD)/$(DOC)-wg-draft.html" + @echo "HTML Editor's Copy -> $(HTML_OUT)" ## ISO-styled Word document (pandoc) -> build/ -docx: $(SRC) $(TEMPLATE) $(TOOLS)/make-iso-reference.py $(TOOLS)/mmark-to-pandoc.py $(TOOLS)/iso-styles.lua +docx: $(SRC) $(REFDOC) $(TOOLS)/mmark-to-pandoc.py $(TOOLS)/iso-styles.lua mkdir -p $(BUILD) - python3 $(TOOLS)/make-iso-reference.py "$(TEMPLATE)" "$(BUILD)/iso-reference.docx" - python3 $(TOOLS)/mmark-to-pandoc.py < $(SRC) > $(BUILD)/$(DOC).pandoc.md + $(PYTHON) $(TOOLS)/mmark-to-pandoc.py < $(SRC) > $(BUILD)/$(DOC).pandoc.md pandoc $(BUILD)/$(DOC).pandoc.md \ - --reference-doc=$(BUILD)/iso-reference.docx \ + --reference-doc=$(REFDOC) \ --lua-filter=$(TOOLS)/iso-styles.lua \ -o $(BUILD)/$(DOC).docx - rm -f $(BUILD)/iso-reference.docx $(BUILD)/$(DOC).pandoc.md + rm -f $(BUILD)/$(DOC).pandoc.md @echo "ISO Word document -> $(BUILD)/$(DOC).docx" clean: diff --git a/README.md b/README.md index a1da5be..2f31e1a 100644 --- a/README.md +++ b/README.md @@ -24,10 +24,11 @@ for more information. ## Specifications -The current Working Group Draft is built automatically from [draft/](draft/) on -every change to the `main` branch: +The Editor's Copy is built automatically from [draft/](draft/) on every change +to the `main` branch. It reflects the latest in-progress edits and is not an +approved Working Group Draft: -- [Digital Credentials Harmonized Presentation — Working Group Draft](https://openid.github.io/dchp/digital-credentials-harmonized-presentation-wg-draft.html) +- [Digital Credentials Harmonized Presentation — Editor's Copy](https://openid.github.io/dchp/digital-credentials-harmonized-presentation-editors-copy.html) ## Contributing diff --git a/draft/digital-credentials-harmonized-presentation.md b/draft/digital-credentials-harmonized-presentation.md index dd740ff..453bfd5 100644 --- a/draft/digital-credentials-harmonized-presentation.md +++ b/draft/digital-credentials-harmonized-presentation.md @@ -1,14 +1,18 @@ %%% -title = "Digital Credentials Harmonized Presentation - Working Group Draft" +title = "Digital Credentials Harmonized Presentation - Editor's Copy" abbrev = "dchp" ipr = "none" workgroup = "Digital Credentials Harmonized Presentation" keyword = ["digital credentials", "mdoc", "sd-jwt vc", "presentation", "iso"] +# seriesInfo is required by mmark; "independent" (non-IETF) silences the +# "Empty 'stream'" warning. The final series metadata for this OpenID/ISO joint +# document is still to be decided by the working group. [seriesInfo] name = "Internet-Draft" value = "digital-credentials-harmonized-presentation" status = "standard" +stream = "independent" [[author]] initials = "TBD" @@ -27,19 +31,17 @@ credentials, bringing together the credential presentation approaches of ISO/IEC 18013-5 and OpenID for Verifiable Presentations across multiple credential formats, including ISO mdoc and IETF SD-JWT VC. -{mainmatter} - -# Foreword +.# Foreword This specification has been jointly developed by members of ISO/IEC JTC 1/SC 17 WG 10 and members of the OpenID Foundation Digital Credentials Protocols (DCP) Working Group, under the OpenID Foundation's Digital Credentials Harmonized Presentation (DCHP) Working Group. -This is a Working Group Draft. It is a work in progress and is subject to change +This is an Editor's Copy. It is a work in progress and is subject to change at any time. -# Introduction +.# Introduction ISO/IEC 18013-5 (Device Request / Device Response) and OpenID for Verifiable Presentations (Authorization Request / Authorization Response) take different @@ -52,6 +54,8 @@ The objective is to enable interoperability among the parties involved in the presentation of digital credentials while allowing existing deployments to continue to operate. +{mainmatter} + # Scope To be completed. diff --git a/template/iso-reference.docx b/template/iso-reference.docx new file mode 100644 index 0000000000000000000000000000000000000000..75b9f96ab10bdb7b43e158df547d8071baaabf09 GIT binary patch literal 43141 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 GefmF0IMNyb literal 0 HcmV?d00001 diff --git a/tests/fixtures/sample.expected.md b/tests/fixtures/sample.expected.md new file mode 100644 index 0000000..448da57 --- /dev/null +++ b/tests/fixtures/sample.expected.md @@ -0,0 +1,35 @@ +--- +title: "Sample Spec - Volume 2" +subtitle: "Editor Copy" +--- + +## Notice + +This frontmatter subsection comes after the abstract and MUST survive: mmark +ends the abstract at the next heading of any level. + + +# Foreword + +Foreword text (unnumbered in both renditions). + +# Scope + +A normative example follows and must pass through verbatim: + +``` +%%% +title = "not the real title" +%%% +{mainmatter} +.# This dot-hash line is example content, not a heading +{: title="keep me"} +``` + +Body continues after the example. + + + +# Bibliography + +[1] Some reference. diff --git a/tests/fixtures/sample.md b/tests/fixtures/sample.md new file mode 100644 index 0000000..ab48b14 --- /dev/null +++ b/tests/fixtures/sample.md @@ -0,0 +1,49 @@ +%%% +title = 'Sample Spec - Volume 2 - Editor Copy' +abbrev = "sample" +ipr = "none" + +[seriesInfo] +name = "Internet-Draft" +value = "sample" +status = "standard" +stream = "independent" +%%% + +.# Abstract + +This abstract must be dropped from the ISO Word output. + +## Notice + +This frontmatter subsection comes after the abstract and MUST survive: mmark +ends the abstract at the next heading of any level. + +{mainmatter} + +.# Foreword + +Foreword text (unnumbered in both renditions). + +# Scope + +A normative example follows and must pass through verbatim: + +``` +%%% +title = "not the real title" +%%% +{mainmatter} +.# This dot-hash line is example content, not a heading +{: title="keep me"} +``` + +Body continues after the example. + +{: .stray-attribute-list} + +{backmatter} + +# Bibliography + +[1] Some reference. diff --git a/tests/test_mmark_to_pandoc.py b/tests/test_mmark_to_pandoc.py new file mode 100644 index 0000000..cc70a3c --- /dev/null +++ b/tests/test_mmark_to_pandoc.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python3 +"""Golden-output test for tools/mmark-to-pandoc.py. + +The converter runs three stacked line transformations (front-matter parsing, +abstract dropping, fence-aware filtering) whose failures are otherwise silent — +a mangled normative example or a dropped subsection produces no build error, only +a wrong Word document. This test pins the converter's output for a sample that +exercises every transformation: + + * a single-quoted TOML title containing embedded `` - `` separators (title vs. + status split); + * an ``.# Abstract`` followed by a ``## Notice`` subsection that must survive; + * a fenced code block whose contents (``%%%``, ``{mainmatter}``, ``.#``, + ``{: ...}``) must pass through verbatim. + +Run: python3 tests/test_mmark_to_pandoc.py (requires Python 3.11+ / tomllib) +""" +from __future__ import annotations + +import difflib +import importlib.util +import pathlib +import sys + +ROOT = pathlib.Path(__file__).resolve().parent.parent +FIXTURES = ROOT / "tests" / "fixtures" + +# The converter's filename has a hyphen, so load it by path rather than import. +_spec = importlib.util.spec_from_file_location( + "mmark_to_pandoc", ROOT / "tools" / "mmark-to-pandoc.py" +) +assert _spec and _spec.loader +mmark_to_pandoc = importlib.util.module_from_spec(_spec) +_spec.loader.exec_module(mmark_to_pandoc) + + +def main() -> int: + source = (FIXTURES / "sample.md").read_text() + expected = (FIXTURES / "sample.expected.md").read_text() + got = mmark_to_pandoc.convert(source) + if got != expected: + sys.stderr.writelines( + difflib.unified_diff( + expected.splitlines(keepends=True), + got.splitlines(keepends=True), + fromfile="sample.expected.md", + tofile="convert(sample.md)", + ) + ) + print("FAIL: mmark-to-pandoc golden output mismatch", file=sys.stderr) + return 1 + print("OK: mmark-to-pandoc golden output matches") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tools/iso-styles.lua b/tools/iso-styles.lua index bd70d14..b992f06 100644 --- a/tools/iso-styles.lua +++ b/tools/iso-styles.lua @@ -1,8 +1,9 @@ --[[ ISO front/back-matter formatting for the pandoc -> Word build. - 1. Title block. mmark-to-pandoc.py carries the mmark title across as pandoc - metadata; here we render it as a centred title (plus a status subtitle) at + 1. Title block. mmark-to-pandoc.py carries the title across as two pandoc + metadata fields (`title` and an optional `subtitle` holding the document + status); here we render them as a centred title plus a status subtitle at the very top. Without this, pandoc + the ISO reference document produce a Word document with no title at all. @@ -64,13 +65,12 @@ end function Pandoc(doc) local meta_title = doc.meta.title if meta_title then - local full = pandoc.utils.stringify(meta_title) - doc.meta.title = nil -- suppress pandoc's own (unstyled) title block - -- Split "Title - Status" into title + subtitle on the first " - ". - local main, sub = full:match("^(.-)%s+%-%s+(.+)$") - if not main then main = full end + local main = pandoc.utils.stringify(meta_title) + local sub = doc.meta.subtitle and pandoc.utils.stringify(doc.meta.subtitle) or nil + doc.meta.title = nil -- suppress pandoc's own (unstyled) title block + doc.meta.subtitle = nil local head = { centred_block(main, "36", true, "120") } -- 18 pt bold - if sub then + if sub and sub ~= "" then head[#head + 1] = centred_block(sub, "26", false, "360") -- 13 pt end for _, b in ipairs(doc.blocks) do diff --git a/tools/make-iso-reference.py b/tools/make-iso-reference.py index f3e1d4c..b9236a3 100644 --- a/tools/make-iso-reference.py +++ b/tools/make-iso-reference.py @@ -13,12 +13,21 @@ (.docx) so the result is a normal Word document; 2. neutralises the ISO copyright notice and document-number placeholders that live in the running headers/footers; and - 3. drops the ISO logo images and the ISO document metadata (docProps and - customXml parts) so no ISO branding or classification travels with the - exported file; and + 3. drops the ISO logo images, the embedded OLE object, and the ISO document + metadata (docProps and customXml parts) — and strips the drawing/object + elements in word/document.xml that referenced them — so no ISO branding or + classification travels with the exported file and it opens clean (no + broken-image placeholders) when viewed standalone; and 4. un-hides the styles (removes ) so every docx viewer applies the ISO heading styles instead of falling back to plain body text. +This script is NOT run on every build. Its output is committed as +``template/iso-reference.docx`` and used directly by ``make docx``; run this +script by hand only to regenerate that file when the ISO template changes. +Note: pandoc derives a single-section layout from the reference document, so the +committed ``iso-reference.docx`` may still benefit from a one-time manual pass in +Word to finalise section setup, page-numbering restarts, and margins. + Everything else (styles, numbering, theme, fonts, headers/footers) is copied through so the document keeps the ISO look-and-feel. @@ -42,7 +51,7 @@ # neutral, non-IPR marker, leaving page-number fields (separate runs) intact. # Matching by content (not exact whitespace) keeps this robust to the template's # non-breaking spaces and dash characters. -DRAFT_LABEL = "Working Group Draft" +DRAFT_LABEL = "Editor's Copy" _RUN_TEXT = re.compile(r"(]*>)([^<]*)()", re.S) @@ -66,12 +75,13 @@ def repl(m: "re.Match[str]") -> str: "wordprocessingml.document.main+xml" ) -# --- Parts to drop entirely (ISO logos and ISO document metadata) ------------ -# The template's only images are the ISO logo/branding, referenced solely by the -# title page (which pandoc discards). customXml holds ISO metadata bindings and -# docProps/{app,custom}.xml hold ISO company/classification metadata. None are -# needed to style the document, so we drop them. -DROP_PREFIXES = ("word/media/", "customXml/") +# --- Parts to drop entirely (ISO logos, OLE object, ISO document metadata) --- +# The template's only images (word/media) are the ISO logo/branding and the +# template's embedded OLE object (word/embeddings/oleObject1.bin), all referenced +# solely by the title page (which pandoc discards). customXml holds ISO metadata +# bindings and docProps/{app,custom}.xml hold ISO company/classification +# metadata. None are needed to style the document, so we drop them. +DROP_PREFIXES = ("word/media/", "word/embeddings/", "customXml/") DROP_EXACT = {"docProps/app.xml", "docProps/custom.xml"} @@ -82,7 +92,13 @@ def _is_dropped(name: str) -> bool: # Substrings that identify a relationship/content-type entry pointing at a # dropped part, so we can keep [Content_Types].xml and the *.rels files # internally consistent after the drop. -_DANGLING = ("media/", "customXml/", "docProps/app.xml", "docProps/custom.xml") +_DANGLING = ( + "media/", + "embeddings/", + "customXml/", + "docProps/app.xml", + "docProps/custom.xml", +) _XML_ELEMENT = re.compile(r"<(Relationship|Override|Default)\b[^>]*/>") @@ -96,6 +112,23 @@ def repl(m: "re.Match[str]") -> str: return _XML_ELEMENT.sub(repl, xml) +# The template's document body (the ISO title page) embeds the logo images via +# and the OLE object via . Pandoc discards the reference +# document's body, so these never reach the generated .docx — but they leave +# dangling image/OLE references (rId25/26/27) behind in the reference file +# itself, which show as broken-image placeholders when it is opened standalone. +# Their target parts are dropped above; strip the referencing elements too so +# the reference document is internally consistent. Only inline content is +# removed; the section properties () that pandoc reads are untouched. +_DRAWING_OR_OBJECT = re.compile( + r"", re.S +) + + +def _strip_media_elements(xml: str) -> str: + return _DRAWING_OR_OBJECT.sub("", xml) + + # The ISO template hides most of its styles from the Word gallery # ( / ). Several docx viewers (Preview/Quick # Look, Pages, Google Docs, some LibreOffice paths) skip the *formatting* of @@ -116,6 +149,8 @@ def transform(name: str, data: bytes) -> bytes: return _strip_dangling(text).encode("utf-8") if name.endswith(".rels"): return _strip_dangling(data.decode("utf-8")).encode("utf-8") + if name == "word/document.xml": + return _strip_media_elements(data.decode("utf-8")).encode("utf-8") if name == "word/styles.xml": return _activate_styles(data.decode("utf-8")).encode("utf-8") if name.startswith(("word/header", "word/footer")) and name.endswith(".xml"): diff --git a/tools/mmark-to-pandoc.py b/tools/mmark-to-pandoc.py index 3501896..4b8c35d 100644 --- a/tools/mmark-to-pandoc.py +++ b/tools/mmark-to-pandoc.py @@ -6,16 +6,25 @@ understand; this filter removes them so the *same* source can also be converted to the ISO Word document by pandoc: - * the ``%%% ... %%%`` TOML front matter block (mmark document metadata) — the - ``title`` is kept and re-emitted as a pandoc YAML metadata block so the Word - document still has a title; + * the ``%%% ... %%%`` TOML front matter block (mmark document metadata) — it is + parsed with ``tomllib`` and the ``title`` is re-emitted as pandoc metadata. + The mmark title encodes the document status after the last `` - `` (e.g. + ``"... - Editor's Copy"``); we split it back into a ``title`` and a + ``subtitle`` so the Word document gets a styled title + status block; * the ``.# Abstract`` section (an RFC/mmark concept; ISO documents have no - abstract, so the whole abstract block is dropped); + abstract, so the whole abstract block is dropped — up to, but not including, + the next heading of *any* level, matching how mmark ends the abstract); * the ``{frontmatter}`` / ``{mainmatter}`` / ``{backmatter}`` part markers; - * defensive clean-up of kramdown-style ``{: ...}`` attribute lists. + * kramdown-style ``{: ...}`` inline-attribute-list lines; + * mmark special headings ``.# Heading`` become normal ``# Heading``. -Everything else (headings, paragraphs, lists, tables, definition lists, notes) -is common Markdown and is passed through unchanged. Reads stdin, writes stdout. +All of the above filtering is skipped *inside fenced code blocks* (``` ``` `` / +``~~~``), so normative request/response examples that happen to contain these +constructs pass through verbatim. Everything else (headings, paragraphs, lists, +tables, definition lists, notes) is common Markdown and is passed through +unchanged. Reads stdin, writes stdout. + +Requires Python 3.11+ for ``tomllib`` (or the ``tomli`` backport on 3.10). """ from __future__ import annotations @@ -23,72 +32,118 @@ import re import sys - -def strip(text: str) -> str: - lines = text.splitlines() +try: + import tomllib +except ModuleNotFoundError: # Python < 3.11 + import tomli as tomllib # type: ignore[no-redef] + + +# An ATX heading, including the mmark special-heading ".#" form. +HEADING = re.compile(r"^\s{0,3}\.?#{1,6}(?:\s|$)") +PART_MARKER = re.compile(r"^\{(?:frontmatter|mainmatter|backmatter)\}\s*$") +ABSTRACT_START = re.compile(r"^\.#\s+Abstract\b") +IAL_LINE = re.compile(r"^\{:.*\}\s*$") # kramdown inline attribute list +FENCE = re.compile(r"^\s*(`{3,}|~{3,})") # opening/closing code fence +SPECIAL_HEADING = re.compile(r"^(\s{0,3})\.(#{1,6})") + + +def _split_front_matter(lines: list[str]) -> tuple[str, int]: + """Return (toml_text, body_start_index) for a leading ``%%% ... %%%`` block. + + If the source does not open with ``%%%`` there is no front matter: the whole + input is the body. + """ + if lines and lines[0].strip() == "%%%": + for j in range(1, len(lines)): + if lines[j].strip() == "%%%": + return "\n".join(lines[1:j]), j + 1 + return "", 0 + + +def _title_and_status(toml_text: str) -> tuple[str | None, str | None]: + """Parse the TOML front matter into (title, status). + + The status (e.g. ``Editor's Copy``) is encoded after the *last* `` - `` in + the single mmark ``title`` so the same title still drives the HTML draft. + Splitting on the last separator keeps titles that themselves contain `` - `` + intact. ``tomllib`` handles basic, literal, and multi-line TOML strings, so + single-quoted titles and escaped quotes are parsed correctly. + """ + if not toml_text.strip(): + return None, None + title = tomllib.loads(toml_text).get("title") + if not isinstance(title, str) or not title.strip(): + return None, None + main, sep, status = title.rpartition(" - ") + if sep: + return main.strip(), status.strip() + return title.strip(), None + + +def _process_body(lines: list[str]) -> list[str]: out: list[str] = [] - - i = 0 - n = len(lines) - - # 1. Drop a leading %%% ... %%% TOML front matter block, but keep the title - # so it can be re-emitted as pandoc metadata (mmark carried the title in - # the front matter; the ISO Word document needs one too). - title = None - if i < n and lines[i].strip() == "%%%": - i += 1 - while i < n and lines[i].strip() != "%%%": - m = re.match(r'\s*title\s*=\s*"(.*)"\s*$', lines[i]) - if m: - title = m.group(1) - i += 1 - i += 1 # skip closing %%% - - part_marker = re.compile(r"^\{(frontmatter|mainmatter|backmatter)\}\s*$") - abstract_start = re.compile(r"^\.#\s+Abstract\b") - ial_line = re.compile(r"^\{:.*\}\s*$") # kramdown inline attribute list - - while i < n: - line = lines[i] - - # 2. Drop the abstract block: from ".# Abstract" up to the next part - # marker or top-level heading (exclusive). - if abstract_start.match(line): - i += 1 - while i < n and not ( - part_marker.match(lines[i]) or lines[i].startswith("# ") - ): - i += 1 + in_fence = False + fence = "" + dropping_abstract = False + + for line in lines: + m = FENCE.match(line) + if m: + marker = m.group(1)[0] * 3 # normalise to ``` or ~~~ + if not in_fence: + in_fence, fence = True, marker + elif marker == fence: + in_fence = False + if not dropping_abstract: + out.append(line) continue - - # 3. Drop mmark part markers entirely. - if part_marker.match(line): - i += 1 + if in_fence: + if not dropping_abstract: + out.append(line) continue - # 4. Drop stray kramdown attribute-list lines. - if ial_line.match(line): - i += 1 - continue + # The abstract runs until the next heading (any level) or part marker; + # everything in between is dropped from the ISO Word output. + if dropping_abstract: + if HEADING.match(line) or PART_MARKER.match(line): + dropping_abstract = False # fall through and handle this line + else: + continue - # Defensive: turn any other ".# Heading" into a normal heading. - line = re.sub(r"^\.#", "#", line) + if ABSTRACT_START.match(line): + dropping_abstract = True + continue + if PART_MARKER.match(line): + continue + if IAL_LINE.match(line): + continue + line = SPECIAL_HEADING.sub(r"\1\2", line) # ".# Heading" -> "# Heading" out.append(line) - i += 1 # Collapse leading blank lines produced by the removals. while out and out[0].strip() == "": out.pop(0) + return out + + +def convert(text: str) -> str: + lines = text.splitlines() + toml_text, start = _split_front_matter(lines) + title, status = _title_and_status(toml_text) - body = "\n".join(out) + "\n" + body = "\n".join(_process_body(lines[start:])) + "\n" - # Re-emit the title as a pandoc YAML metadata block. The Lua filter turns it - # into the document's title block; here we just carry it across. + # Re-emit the title (and status subtitle) as a pandoc YAML metadata block; + # the Lua filter renders them as the Word document's title block. if title: - body = "---\ntitle: " + json.dumps(title) + "\n---\n\n" + body + meta = ["---", "title: " + json.dumps(title)] + if status: + meta.append("subtitle: " + json.dumps(status)) + meta += ["---", "", ""] + body = "\n".join(meta) + body return body if __name__ == "__main__": - sys.stdout.write(strip(sys.stdin.read())) + sys.stdout.write(convert(sys.stdin.read())) From 5f6edf201c728056cdf13e0a146b84e18177889e Mon Sep 17 00:00:00 2001 From: Oliver Terbu Date: Tue, 7 Jul 2026 08:39:07 +0200 Subject: [PATCH 03/13] fix(build): make html/docx work out of the box - seriesInfo stream independent conflicted with xml2rfc's default submissionType (IETF) and failed # Clean stale intermediates first so the copy below is unambiguous even if a # previous run was interrupted. rm -f draft/digital-credentials-harmonized-presentation*.html draft/digital-credentials-harmonized-presentation*.xml draft/digital-credentials-harmonized-presentation*.txt docker run --rm -v "/Users/oliver.terbu/git/openid/dchp/draft:/data" danielfett/markdown2rfc@sha256:7b4412559d6ba5db45a14174a28da5b240512e7c2a886a5e4adb44e5e67f34ca digital-credentials-harmonized-presentation.md Dockerfile danielfett/markdown2rfc Using versions: mmark: 2.2.31 xml2rfc 3.16.0 --------------------- mkdir -p build # Cleaned above, so this glob now matches exactly the fresh output whatever # markdown2rfc names it (it may add a draft-version suffix). cp draft/digital-credentials-harmonized-presentation*.html build/digital-credentials-harmonized-presentation-editors-copy.html rm -f draft/digital-credentials-harmonized-presentation*.html draft/digital-credentials-harmonized-presentation*.xml draft/digital-credentials-harmonized-presentation*.txt HTML Editor's Copy -> build/digital-credentials-harmonized-presentation-editors-copy.html; set stream = IETF to clear the empty-stream warning without the conflict (finding 9) - Makefile: auto-detect a tomllib-capable Python (3.11+) so mkdir -p build python3.13 tools/mmark-to-pandoc.py < draft/digital-credentials-harmonized-presentation.md > build/digital-credentials-harmonized-presentation.pandoc.md pandoc build/digital-credentials-harmonized-presentation.pandoc.md \ --reference-doc=template/iso-reference.docx \ --lua-filter=tools/iso-styles.lua \ -o build/digital-credentials-harmonized-presentation.docx rm -f build/digital-credentials-harmonized-presentation.pandoc.md ISO Word document -> build/digital-credentials-harmonized-presentation.docx works where the default python3 is older, with a clear error if none is found - Makefile: revert the HTML copy to a glob (markdown2rfc may add a draft-version suffix); the clean-before-build step keeps it unambiguous (finding 13) --- Makefile | 22 ++++++++++++++----- ...tal-credentials-harmonized-presentation.md | 10 +++++---- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/Makefile b/Makefile index 2a60610..87ecd2a 100644 --- a/Makefile +++ b/Makefile @@ -6,9 +6,8 @@ # make clean - remove the build/ directory # # Source scripts live in tools/; everything generated goes to build/ (which is -# git-ignored). `make html` needs Docker; `make docx` needs pandoc and python3 -# (3.11+, for the tomllib-based converter). Override the interpreter with e.g. -# `make docx PYTHON=python3.13` on hosts whose default python3 is older. +# git-ignored). `make html` needs Docker; `make docx` needs pandoc and a Python +# with tomllib (3.11+, for the converter). SRCDIR := draft DOC := digital-credentials-harmonized-presentation @@ -16,7 +15,14 @@ SRC := $(SRCDIR)/$(DOC).md TOOLS := tools BUILD := build -PYTHON ?= python3 + +# The mmark->pandoc converter needs tomllib (Python 3.11+). Auto-pick the first +# available interpreter that has it, so `make docx` works even where the default +# python3 is older; override explicitly with `make docx PYTHON=/path/to/python`. +PYTHON ?= $(shell for p in python3 python3.13 python3.12 python3.11; do \ + command -v $$p >/dev/null 2>&1 && $$p -c 'import tomllib' >/dev/null 2>&1 \ + && { echo $$p; exit 0; }; \ + done) # Pinned by digest so the HTML rendering is reproducible: an untagged/:latest # image could silently change the output between identical commits. Regenerate @@ -41,12 +47,18 @@ html: $(SRC) rm -f $(SRCDIR)/$(DOC)*.html $(SRCDIR)/$(DOC)*.xml $(SRCDIR)/$(DOC)*.txt docker run --rm -v "$(CURDIR)/$(SRCDIR):/data" $(MD2RFC_IMAGE) $(DOC).md mkdir -p $(BUILD) - cp $(SRCDIR)/$(DOC).html $(HTML_OUT) + # Cleaned above, so this glob now matches exactly the fresh output whatever + # markdown2rfc names it (it may add a draft-version suffix). + cp $(SRCDIR)/$(DOC)*.html $(HTML_OUT) rm -f $(SRCDIR)/$(DOC)*.html $(SRCDIR)/$(DOC)*.xml $(SRCDIR)/$(DOC)*.txt @echo "HTML Editor's Copy -> $(HTML_OUT)" ## ISO-styled Word document (pandoc) -> build/ docx: $(SRC) $(REFDOC) $(TOOLS)/mmark-to-pandoc.py $(TOOLS)/iso-styles.lua + @test -n "$(strip $(PYTHON))" || { \ + echo "error: no Python with tomllib (3.11+) found;"; \ + echo " install one or run: make docx PYTHON=/path/to/python3.11+"; \ + exit 1; } mkdir -p $(BUILD) $(PYTHON) $(TOOLS)/mmark-to-pandoc.py < $(SRC) > $(BUILD)/$(DOC).pandoc.md pandoc $(BUILD)/$(DOC).pandoc.md \ diff --git a/draft/digital-credentials-harmonized-presentation.md b/draft/digital-credentials-harmonized-presentation.md index 453bfd5..e59c55e 100644 --- a/draft/digital-credentials-harmonized-presentation.md +++ b/draft/digital-credentials-harmonized-presentation.md @@ -5,14 +5,16 @@ ipr = "none" workgroup = "Digital Credentials Harmonized Presentation" keyword = ["digital credentials", "mdoc", "sd-jwt vc", "presentation", "iso"] -# seriesInfo is required by mmark; "independent" (non-IETF) silences the -# "Empty 'stream'" warning. The final series metadata for this OpenID/ISO joint -# document is still to be decided by the working group. +# seriesInfo is placeholder IETF Internet-Draft framing (see name below). The +# stream must match the document's submissionType (IETF by default), so it is set +# to "IETF" to clear mmark's "Empty 'stream'" warning without an xml2rfc +# stream/submissionType conflict. The final series metadata for this OpenID/ISO +# joint document is still to be decided by the working group. [seriesInfo] name = "Internet-Draft" value = "digital-credentials-harmonized-presentation" status = "standard" -stream = "independent" +stream = "IETF" [[author]] initials = "TBD" From fe3cf56eebe2f27e97d81ab790579cf57ff95fca Mon Sep 17 00:00:00 2001 From: Oliver Terbu Date: Tue, 7 Jul 2026 17:45:25 +0200 Subject: [PATCH 04/13] fix(draft): don't assert an IETF stream on an OIDF/ISO document mmark hard-codes the rfc submissionType as IETF and won't accept a non-IETF stream, so the previous stream = IETF was misleading and stream = independent fails the build. Leave stream unset (benign Empty-stream warning, HTML still builds) and note that the real publication reference is a WG/SDO decision (issue 13). --- .../digital-credentials-harmonized-presentation.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/draft/digital-credentials-harmonized-presentation.md b/draft/digital-credentials-harmonized-presentation.md index e59c55e..40d260b 100644 --- a/draft/digital-credentials-harmonized-presentation.md +++ b/draft/digital-credentials-harmonized-presentation.md @@ -5,16 +5,18 @@ ipr = "none" workgroup = "Digital Credentials Harmonized Presentation" keyword = ["digital credentials", "mdoc", "sd-jwt vc", "presentation", "iso"] -# seriesInfo is placeholder IETF Internet-Draft framing (see name below). The -# stream must match the document's submissionType (IETF by default), so it is set -# to "IETF" to clear mmark's "Empty 'stream'" warning without an xml2rfc -# stream/submissionType conflict. The final series metadata for this OpenID/ISO -# joint document is still to be decided by the working group. +# NOTE: this [seriesInfo] block is IETF Internet-Draft scaffolding needed by the +# markdown2rfc (mmark/xml2rfc) HTML toolchain; it is NOT a claim that this is an +# IETF document. mmark 2.2.31 hard-codes and does not +# propagate the stream, so a non-IETF stream (e.g. "independent") makes xml2rfc +# fail with a stream/submissionType mismatch. We therefore leave "stream" unset +# rather than assert a false "IETF" stream; mmark only warns ("Empty 'stream'") +# and the HTML still builds. The real publication reference for this OpenID/ISO +# joint document is a WG/SDO decision -- see issue 13. [seriesInfo] name = "Internet-Draft" value = "digital-credentials-harmonized-presentation" status = "standard" -stream = "IETF" [[author]] initials = "TBD" From 4dd687eb91a20380db850875b16d2fee0b514a50 Mon Sep 17 00:00:00 2001 From: Joseph Heenan Date: Tue, 7 Jul 2026 16:55:38 +0100 Subject: [PATCH 05/13] fix(ci): trigger the build workflow on tests/** changes The build job runs tests/test_mmark_to_pandoc.py, but the push and pull_request paths filters did not include tests/**, so a PR touching only the test or its golden fixtures merged with no CI run at all; a fixture/converter mismatch would only surface on the next unrelated PR. Co-Authored-By: Claude Fable 5 --- .github/workflows/build.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a7d4644..e12c401 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -6,6 +6,7 @@ on: - 'draft/**' - 'tools/**' - 'template/**' + - 'tests/**' - 'Makefile' - '.github/workflows/build.yml' pull_request: @@ -13,6 +14,7 @@ on: - 'draft/**' - 'tools/**' - 'template/**' + - 'tests/**' - 'Makefile' - '.github/workflows/build.yml' workflow_dispatch: From 396bd324c80b33ebd67335ebe299564468c78e2f Mon Sep 17 00:00:00 2001 From: Joseph Heenan Date: Tue, 7 Jul 2026 18:30:22 +0100 Subject: [PATCH 06/13] fix(template): sanitize the committed pandoc reference docx MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The committed template/iso-reference.docx shipped three things from the ISO Word template that the generator's docstring promised were stripped: * docProps/core.xml with the template author's personal metadata (dc:creator / cp:lastModifiedBy) — a third party's personal data in a public repo; only app.xml and custom.xml were dropped. Drop the whole docProps/ tree (Word and pandoc do not need document properties) and strip the package references to it. * the entire ISO template body: title page, WD/CD review warning, and the full ISO copyright/IPR notice (© ISO, "All rights reserved…", the ISO copyright office address). Pandoc ignores the reference document's body — it reads the styles and the body-level only — so replace the body with one empty paragraph plus that sectPr, and strip drawing/object elements from the kept headers/footers instead (their media relationships were already being removed). * [trash]/0000.dat and [trash]/0001.dat — zip-housekeeping junk left in the .dotx by whatever tool last edited the ISO template. Add tests/test_iso_reference.py pinning all of this against the committed file (docProps absence, ISO-boilerplate tripwires, junk entries, sectPr preservation) and run it in CI. Co-Authored-By: Claude Fable 5 --- .github/workflows/build.yml | 6 ++- template/iso-reference.docx | Bin 43141 -> 25921 bytes tests/test_iso_reference.py | 102 ++++++++++++++++++++++++++++++++++++ tools/make-iso-reference.py | 68 +++++++++++++++--------- 4 files changed, 149 insertions(+), 27 deletions(-) create mode 100644 tests/test_iso_reference.py diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e12c401..e236df7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -43,8 +43,10 @@ jobs: with: python-version: '3.12' - - name: Test the mmark->pandoc converter - run: python3 tests/test_mmark_to_pandoc.py + - name: Run tests + run: | + python3 tests/test_mmark_to_pandoc.py + python3 tests/test_iso_reference.py # ISO-styled Word document (pandoc), for sharing the draft with ISO. # Delivered as a workflow artifact only; not published to the site. diff --git a/template/iso-reference.docx b/template/iso-reference.docx index 75b9f96ab10bdb7b43e158df547d8071baaabf09..5797535309a93843f75d0a1e4263f7cb65b3c78a 100644 GIT binary patch delta 1820 zcmZuy2T+sQ8V!F428aRaHS{8c0HI0MFaf0bScTA4geWBmC0PUnfBJ$;dGQAXm6`+u zAxIb3St$x43WAHe!qN?^k+L)uT-o64yf<&>-8=W5xifdZGUuLiv&MklCql|z?r>ol z7z~CKEDczX_|dr=LNM41F&Io5;?~H4k4lDUeI}CGtHnELW^<0h?==mYF~7%Qi`~?E zVs&RN8_TLqKjj0baKfuo%6MZ+JDwW35l}@_)@6kV^ zcGHOHvXN4AC8<`xB|C$E`)h(v>W|4?@uFp#a)m`U4v0rV6tU-*b*xCHn_cmap!mRp zUxmY)Uv$?^g$w6Xv1X5}_E6S0T=Pzse2pbv74xjAYv2-fGG()5ZdRYErI#-g-sp}Z zet5c8wr7s#Sep!|3=$%-Ob#*B7}_Q{FP%x8enoat1SSq)$apYcN-3Rc*3tu)>Y8-$348fvDQ)GLm0^uk{{AUvZTrBYjVH{kd6j`d0Zh z`Cpw5=%{eD?+z<_cZb-i?_}EuTMiMj5Nx#$|1V+^R~84VEd2(}1$5^*WyQ|1>Zq$_};^f<|j0z3h>~UHXb->vd0bingV7dkA~J~ z+Mp~25jG#>cm?n6=>!Z?n2bN&Z9WP8FMGw&1*cjCJQ@lZ6c^x`I5LH9jN9MyM{Uu4 zCMc0v)m&TRbVPtirWb@m)*Bie$dR&Y3dgwhvAXo&a3@q=CkU4Cc@esCgOV;66hUt% zQQ;;=Wc&4AjxBZj*f}Xu?vQ_&NoZzvv+tIo9&`PqinE$4i4tbQUX&u zY~mD{af;?!j;FJn?->oN9;_5u%W@5Pq~6{Dj6cKdwFJ);N)?`%M!nfwc<8G+tQqcs z!r=59rk6F_jwO1^8?VcM>#glQa2x=K(-K=za2V{apsPcI#x6$3g&BuMhkj2b(-^n} zDuq)rNt;p^yi&t0RqTx3r#nBjG*N0)iVWnNNUl3D>}4#?d}LYNv`pmT;YTUHmawecWUhff3^=6?&O%`^hca}Uv7iU!@Ju&{xyCI z5mA4{upN=(=(KyaaoL-XcRbE4ifzOtI;pAJc>CE*o;xJr4V= z3d!3xevorPzX5Q0-9`B4v7h@n_^!k=5}x zY5haGnA-(y@2wT>=blQ_fvO3APtnl&&*CleCvL)S4|n)Ps}Fk{?TV~K1QH5sjhKlB&YR8wyGU;GCYfqmaA|3`FlIIC z2g`BP^wCdq^FJ#mBecwR))uFmN-Qko9L+xWoztBI`;bYRDOiHeq^Uj{k9To(IAwfi z%%$>_+=9WYI=PV05Z+vyYBpF<#mJC;=}|PqH!e*Dc=xJ5H?3&Bo>?(igKwa(`Oaql z)-}@-OA^=b=B{ER3tP1j7t=XWNrBZJv;CBj`4Bw06pwsdEPb7}fFSlUuDW%V$JeXL zf>u$){(OJnA@zLD<6=dusSw%sbQJ94QuH|6%?sO;NF6?zJ-)*J!q07wXqfm~RU zD*sgDf3Na{Av()mQbX{We3R(;=x7Ex&K&xKBLn@yQUaRAp*5Brpd$g{*bacW6hvd& z1Isc{6Wb0Ll85*LP*;WybL;?p40J&NS{e{X02SKMf&j4kkVdT?pl1l3sU;jDnf{%H zPG&G7Xcy?;(h%yDfLqp3aGe8iiwKq1*#hMO(3`qr*s`Gi6h&irhn%C3_l?8L9U-z` QzpP-IB4DtcGyA8%03T@)XaE2J delta 19212 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 list[str]: + """No docProps/ part may ship: core.xml carries the ISO template author's + personal metadata (dc:creator / cp:lastModifiedBy), app.xml and custom.xml + the ISO company/classification metadata. Nothing may still reference them. + """ + problems = [ + f"{name}: docProps part shipped" + for name in z.namelist() + if name.startswith("docProps/") + ] + for name in ("[Content_Types].xml", "_rels/.rels"): + if b"docProps/" in z.read(name): + problems.append(f"{name}: dangling docProps reference") + return problems + + +# Tripwire strings from the ISO title page, copyright/IPR notice, and the +# document-number placeholders. Any one of them surviving in any XML part +# means ISO boilerplate leaked into the public file. +ISO_BOILERPLATE_MARKERS = ( + "© ISO", + "All rights reserved", + "copyright@iso.org", + "ISO copyright office", + "#####", +) + + +def check_no_iso_boilerplate(z: zipfile.ZipFile) -> list[str]: + """No ISO copyright/IPR or title-page text may remain in any XML part + (body, headers, footers, notes); footers carry the draft label instead.""" + return [ + f"{name}: contains {marker!r}" + for name in z.namelist() + if name.endswith(".xml") + for marker in ISO_BOILERPLATE_MARKERS + if marker in z.read(name).decode("utf-8") + ] + + +def check_no_trash_entries(z: zipfile.ZipFile) -> list[str]: + """The source .dotx contains [trash]/*.dat zip-housekeeping junk left by + whatever tool last edited the ISO template; it must not be copied over.""" + return [ + f"{name}: junk zip entry copied from the template" + for name in z.namelist() + if name.startswith("[trash]/") + ] + + +def check_section_properties_kept(z: zipfile.ZipFile) -> list[str]: + """pandoc derives page geometry and the running footers from the body-level + ; cleaning the body must not lose it.""" + doc = z.read("word/document.xml").decode("utf-8") + return [] if " lost"] + + +CHECKS = [ + check_no_document_properties, + check_no_iso_boilerplate, + check_no_trash_entries, + check_section_properties_kept, +] + + +def main() -> int: + with zipfile.ZipFile(REFDOC) as z: + problems = [p for check in CHECKS for p in check(z)] + for p in problems: + print(f"FAIL: {p}", file=sys.stderr) + if problems: + print(f"FAIL: {REFDOC.relative_to(ROOT)} is not clean", file=sys.stderr) + return 1 + print("OK: iso-reference.docx hygiene checks pass") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tools/make-iso-reference.py b/tools/make-iso-reference.py index b9236a3..1f01158 100644 --- a/tools/make-iso-reference.py +++ b/tools/make-iso-reference.py @@ -11,14 +11,16 @@ This script therefore: 1. flips the main-document content type from *template* (.dotx) to *document* (.docx) so the result is a normal Word document; - 2. neutralises the ISO copyright notice and document-number placeholders that - live in the running headers/footers; and - 3. drops the ISO logo images, the embedded OLE object, and the ISO document - metadata (docProps and customXml parts) — and strips the drawing/object - elements in word/document.xml that referenced them — so no ISO branding or - classification travels with the exported file and it opens clean (no - broken-image placeholders) when viewed standalone; and - 4. un-hides the styles (removes ) so every docx viewer applies + 2. replaces the document body — the ISO title page with its document-number + placeholders, WD/CD review warning, and the full ISO copyright/IPR + notice — with a single empty paragraph, keeping only the body-level + (page geometry, running footers) that pandoc reads; + 3. neutralises the ISO copyright notice and document-number placeholders that + live in the running headers/footers; + 4. drops the ISO logo images, the embedded OLE object, and the ISO document + metadata (docProps and customXml parts) so no ISO branding, classification, + or template-author identity travels with the exported file; and + 5. un-hides the styles (removes ) so every docx viewer applies the ISO heading styles instead of falling back to plain body text. This script is NOT run on every build. Its output is committed as @@ -79,14 +81,17 @@ def repl(m: "re.Match[str]") -> str: # The template's only images (word/media) are the ISO logo/branding and the # template's embedded OLE object (word/embeddings/oleObject1.bin), all referenced # solely by the title page (which pandoc discards). customXml holds ISO metadata -# bindings and docProps/{app,custom}.xml hold ISO company/classification -# metadata. None are needed to style the document, so we drop them. -DROP_PREFIXES = ("word/media/", "word/embeddings/", "customXml/") -DROP_EXACT = {"docProps/app.xml", "docProps/custom.xml"} +# bindings; docProps holds the ISO company/classification metadata (app.xml, +# custom.xml) and the template author's personal metadata (core.xml carries +# dc:creator / cp:lastModifiedBy). None are needed to style the document, so we +# drop them all — Word and pandoc are fine without document properties. +# [trash]/ is zip-housekeeping junk left in the .dotx by whatever tool last +# edited the ISO template; it is not part of the OPC package. +DROP_PREFIXES = ("word/media/", "word/embeddings/", "customXml/", "docProps/", "[trash]/") def _is_dropped(name: str) -> bool: - return name in DROP_EXACT or name.startswith(DROP_PREFIXES) + return name.startswith(DROP_PREFIXES) # Substrings that identify a relationship/content-type entry pointing at a @@ -96,8 +101,7 @@ def _is_dropped(name: str) -> bool: "media/", "embeddings/", "customXml/", - "docProps/app.xml", - "docProps/custom.xml", + "docProps/", ) _XML_ELEMENT = re.compile(r"<(Relationship|Override|Default)\b[^>]*/>") @@ -112,14 +116,27 @@ def repl(m: "re.Match[str]") -> str: return _XML_ELEMENT.sub(repl, xml) -# The template's document body (the ISO title page) embeds the logo images via -# and the OLE object via . Pandoc discards the reference -# document's body, so these never reach the generated .docx — but they leave -# dangling image/OLE references (rId25/26/27) behind in the reference file -# itself, which show as broken-image placeholders when it is opened standalone. -# Their target parts are dropped above; strip the referencing elements too so -# the reference document is internally consistent. Only inline content is -# removed; the section properties () that pandoc reads are untouched. +# The template's document body is the ISO title page: document-number +# placeholders, the WD/CD review warning, the full ISO copyright/IPR notice, +# and the logo drawings / OLE object that referenced the dropped media parts. +# Pandoc ignores the reference document's body — it reads only the styles and +# the final body-level — so replace the whole body with one empty +# paragraph plus that . Nothing ISO-owned survives in the committed +# file when it is opened standalone, and no dangling image references are left +# behind. (Both .* are greedy, so the match keeps the *last* sectPr: the +# body-level one.) +_BODY = re.compile(r"().*()\s*()", re.S) + + +def _empty_body(xml: str) -> str: + return _BODY.sub(r"\1\2\3", xml) + + +# Belt-and-braces for the header/footer parts we keep: _strip_dangling removes +# media/OLE relationships from *all* .rels files, so also strip any +# // elements there (none in the current template) +# rather than leave dangling references that render as broken-image +# placeholders. _DRAWING_OR_OBJECT = re.compile( r"", re.S ) @@ -150,11 +167,12 @@ def transform(name: str, data: bytes) -> bytes: if name.endswith(".rels"): return _strip_dangling(data.decode("utf-8")).encode("utf-8") if name == "word/document.xml": - return _strip_media_elements(data.decode("utf-8")).encode("utf-8") + return _empty_body(data.decode("utf-8")).encode("utf-8") if name == "word/styles.xml": return _activate_styles(data.decode("utf-8")).encode("utf-8") if name.startswith(("word/header", "word/footer")) and name.endswith(".xml"): - return _neutralise_runs(data.decode("utf-8")).encode("utf-8") + text = _strip_media_elements(data.decode("utf-8")) + return _neutralise_runs(text).encode("utf-8") return data From 03ea6814ead14854c93553a21f50e70738debe83 Mon Sep 17 00:00:00 2001 From: Joseph Heenan Date: Tue, 7 Jul 2026 17:08:05 +0100 Subject: [PATCH 07/13] fix(tools): track code fences per CommonMark in the pandoc converter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The fence tracking normalised every fence to three characters and accepted any indentation, so three distinct kinds of legal fence content desynced the open/closed state and mmark filtering ran inside verbatim examples (or, inverted, {backmatter}/{: ...} lines leaked into the Word document as literal text): * a 3-backtick line inside a 4+-backtick fence was taken as the closer (CommonMark: a closer must be at least as long as the opener); * a '```lang' line inside an open fence was taken as the closer (closing fences must be bare — no info string); * backticks indented 4+ spaces (indented-code content) toggled fence state (fences may be indented at most 3 spaces). Track the opener's marker verbatim and require closers to match its character, meet its length, and carry no info string; also reject backtick openers whose info string contains a backtick. Each rule is pinned by a new focused test case. Co-Authored-By: Claude Fable 5 --- tests/test_mmark_to_pandoc.py | 72 +++++++++++++++++++++++++++++------ tools/mmark-to-pandoc.py | 21 ++++++++-- 2 files changed, 78 insertions(+), 15 deletions(-) diff --git a/tests/test_mmark_to_pandoc.py b/tests/test_mmark_to_pandoc.py index cc70a3c..52c4c36 100644 --- a/tests/test_mmark_to_pandoc.py +++ b/tests/test_mmark_to_pandoc.py @@ -34,22 +34,72 @@ _spec.loader.exec_module(mmark_to_pandoc) -def main() -> int: +# Focused cases for the individual line transformations: (name, source, +# expected convert() output). Each pins one behavior the golden sample does +# not exercise; the fence cases follow CommonMark's fence rules, which mmark +# inherits. +CASES: list[tuple[str, str, str]] = [ + ( + "inner 3-backtick line does not close a 4-backtick fence " + "(closer must be at least as long as the opener)", + "# Scope\n\n````\n```\n{: #example-ial}\n```\n````\n\n{backmatter}\n\n# After\n", + "# Scope\n\n````\n```\n{: #example-ial}\n```\n````\n\n\n# After\n", + ), + ( + "a fence line with an info string does not close an open fence " + "(closing fences must be bare)", + '# Scope\n\n```\n```python\n{mainmatter}\n.# not a heading\n```\n\nAfter text.\n', + '# Scope\n\n```\n```python\n{mainmatter}\n.# not a heading\n```\n\nAfter text.\n', + ), + ( + "backticks indented 4+ spaces are indented-code content, not a fence", + "# Scope\n\n ```\n\n{mainmatter}\n\nAfter text.\n", + "# Scope\n\n ```\n\n\nAfter text.\n", + ), + ( + "an ordinary fenced block with an info string still passes through " + "and filtering resumes after it", + '```json\n{"a": 1}\n```\n\n{: .x}\n', + '```json\n{"a": 1}\n```\n\n', + ), +] + + +def _diff(name: str, expected: str, got: str) -> str: + return "".join( + difflib.unified_diff( + expected.splitlines(keepends=True), + got.splitlines(keepends=True), + fromfile=f"expected [{name}]", + tofile="got", + ) + ) + + +def check_golden() -> list[str]: source = (FIXTURES / "sample.md").read_text() expected = (FIXTURES / "sample.expected.md").read_text() got = mmark_to_pandoc.convert(source) if got != expected: - sys.stderr.writelines( - difflib.unified_diff( - expected.splitlines(keepends=True), - got.splitlines(keepends=True), - fromfile="sample.expected.md", - tofile="convert(sample.md)", - ) - ) - print("FAIL: mmark-to-pandoc golden output mismatch", file=sys.stderr) + return ["golden output mismatch:\n" + _diff("sample.md", expected, got)] + return [] + + +def check_cases() -> list[str]: + return [ + f"{name}:\n" + _diff(name, expected, got) + for name, source, expected in CASES + if (got := mmark_to_pandoc.convert(source)) != expected + ] + + +def main() -> int: + failures = check_golden() + check_cases() + for failure in failures: + print(f"FAIL: {failure}", file=sys.stderr) + if failures: return 1 - print("OK: mmark-to-pandoc golden output matches") + print(f"OK: mmark-to-pandoc golden output and {len(CASES)} cases match") return 0 diff --git a/tools/mmark-to-pandoc.py b/tools/mmark-to-pandoc.py index 4b8c35d..1aad324 100644 --- a/tools/mmark-to-pandoc.py +++ b/tools/mmark-to-pandoc.py @@ -43,7 +43,10 @@ PART_MARKER = re.compile(r"^\{(?:frontmatter|mainmatter|backmatter)\}\s*$") ABSTRACT_START = re.compile(r"^\.#\s+Abstract\b") IAL_LINE = re.compile(r"^\{:.*\}\s*$") # kramdown inline attribute list -FENCE = re.compile(r"^\s*(`{3,}|~{3,})") # opening/closing code fence +# A code-fence line (CommonMark rules, which mmark inherits): three or more +# backticks/tildes indented at most 3 spaces — deeper indentation makes the +# line indented-code *content*, not a fence. group(2) is the info string. +FENCE = re.compile(r"^ {0,3}(`{3,}|~{3,})(.*)$") SPECIAL_HEADING = re.compile(r"^(\s{0,3})\.(#{1,6})") @@ -89,10 +92,20 @@ def _process_body(lines: list[str]) -> list[str]: for line in lines: m = FENCE.match(line) if m: - marker = m.group(1)[0] * 3 # normalise to ``` or ~~~ + marker, info = m.group(1), m.group(2) if not in_fence: - in_fence, fence = True, marker - elif marker == fence: + # A backtick fence's info string may not contain backticks + # (such a line is a paragraph, not a fence); tilde fences + # carry no such restriction. + if marker[0] == "~" or "`" not in info: + in_fence, fence = True, marker + elif ( + marker[0] == fence[0] + and len(marker) >= len(fence) + and not info.strip() + ): + # A closer must use the opener's character, be at least as + # long, and be bare; any other fence-looking line is content. in_fence = False if not dropping_abstract: out.append(line) From f2efb4b25674f04d82cb2960a45bc0d14d968fc7 Mon Sep 17 00:00:00 2001 From: Joseph Heenan Date: Tue, 7 Jul 2026 17:19:27 +0100 Subject: [PATCH 08/13] fix(tools): make front-matter splitting robust in the pandoc converter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three independent ways the %%% ... %%% detection deviated from how mmark reads the file, each leaking or crashing instead of converting: * a UTF-8 BOM (e.g. from Notepad) hid the opening delimiter — str.strip() does not remove U+FEFF — so the raw TOML (title, seriesInfo, author emails) was emitted verbatim as Word body text; * a '%%%' line inside a TOML multi-line string was taken as the closing delimiter, so tomllib crashed on the truncated block even though mmark parses the same file fine; * an unclosed block was silently treated as body, again rendering the raw TOML into the Word document. Strip a leading BOM, find the closing delimiter by parsing (a candidate that leaves invalid TOML behind is not the closer), and raise a clear error for unclosed/invalid front matter so `make docx` fails loudly rather than shipping a garbled deliverable. Co-Authored-By: Claude Fable 5 --- tests/test_mmark_to_pandoc.py | 36 ++++++++++++++++++++++-- tools/mmark-to-pandoc.py | 53 ++++++++++++++++++++++------------- 2 files changed, 68 insertions(+), 21 deletions(-) diff --git a/tests/test_mmark_to_pandoc.py b/tests/test_mmark_to_pandoc.py index 52c4c36..de0a4a6 100644 --- a/tests/test_mmark_to_pandoc.py +++ b/tests/test_mmark_to_pandoc.py @@ -62,9 +62,27 @@ '```json\n{"a": 1}\n```\n\n{: .x}\n', '```json\n{"a": 1}\n```\n\n', ), + ( + "a UTF-8 BOM does not hide the front-matter delimiter", + '%%%\ntitle = "T - S"\n%%%\n\n# Scope\n', + '---\ntitle: "T"\nsubtitle: "S"\n---\n\n# Scope\n', + ), + ( + "a %%% line inside a TOML multi-line string does not close the " + "front matter", + '%%%\ntitle = "T - S"\ndescription = """\n%%%\n"""\n%%%\n\n# Scope\n', + '---\ntitle: "T"\nsubtitle: "S"\n---\n\n# Scope\n', + ), ] +def _convert_or_error(source: str) -> str: + try: + return mmark_to_pandoc.convert(source) + except Exception as e: # an unexpected raise is a failure; show it as one + return f"\n" + + def _diff(name: str, expected: str, got: str) -> str: return "".join( difflib.unified_diff( @@ -89,12 +107,26 @@ def check_cases() -> list[str]: return [ f"{name}:\n" + _diff(name, expected, got) for name, source, expected in CASES - if (got := mmark_to_pandoc.convert(source)) != expected + if (got := _convert_or_error(source)) != expected ] +def check_unclosed_front_matter() -> list[str]: + """A ``%%%`` block that never closes must fail the build loudly — both + silently dropping the rest of the file and emitting the raw TOML (author + emails and all) as Word body text are wrong.""" + source = '%%%\ntitle = "T"\n\n# Scope\n' + try: + mmark_to_pandoc.convert(source) + except ValueError as e: + if "front matter" in str(e): + return [] + return [f"unclosed front matter: error message lacks context: {e}"] + return ["unclosed front matter: convert() did not raise"] + + def main() -> int: - failures = check_golden() + check_cases() + failures = check_golden() + check_cases() + check_unclosed_front_matter() for failure in failures: print(f"FAIL: {failure}", file=sys.stderr) if failures: diff --git a/tools/mmark-to-pandoc.py b/tools/mmark-to-pandoc.py index 1aad324..30341de 100644 --- a/tools/mmark-to-pandoc.py +++ b/tools/mmark-to-pandoc.py @@ -50,31 +50,41 @@ SPECIAL_HEADING = re.compile(r"^(\s{0,3})\.(#{1,6})") -def _split_front_matter(lines: list[str]) -> tuple[str, int]: - """Return (toml_text, body_start_index) for a leading ``%%% ... %%%`` block. +def _split_front_matter(lines: list[str]) -> tuple[dict, int]: + """Return (metadata, body_start_index) for a leading ``%%% ... %%%`` block. If the source does not open with ``%%%`` there is no front matter: the whole - input is the body. + input is the body. The closing delimiter is found by parsing: a ``%%%`` line + *inside* a TOML multi-line string does not close the block (the truncated + text is not valid TOML there), matching how mmark reads the file. A block + that never closes, or whose content is not valid TOML, raises ValueError so + the build fails loudly instead of leaking the metadata (author emails and + all) into the Word document body. """ - if lines and lines[0].strip() == "%%%": - for j in range(1, len(lines)): - if lines[j].strip() == "%%%": - return "\n".join(lines[1:j]), j + 1 - return "", 0 + if not lines or lines[0].strip() != "%%%": + return {}, 0 + error: Exception | None = None + for j in range(1, len(lines)): + if lines[j].strip() != "%%%": + continue + try: + return tomllib.loads("\n".join(lines[1:j])), j + 1 + except tomllib.TOMLDecodeError as e: + error = e + if error is not None: + raise ValueError(f"front matter is not valid TOML: {error}") + raise ValueError("front matter opened with '%%%' is never closed") -def _title_and_status(toml_text: str) -> tuple[str | None, str | None]: - """Parse the TOML front matter into (title, status). +def _title_and_status(meta: dict) -> tuple[str | None, str | None]: + """Extract (title, status) from the parsed front matter. The status (e.g. ``Editor's Copy``) is encoded after the *last* `` - `` in the single mmark ``title`` so the same title still drives the HTML draft. Splitting on the last separator keeps titles that themselves contain `` - `` - intact. ``tomllib`` handles basic, literal, and multi-line TOML strings, so - single-quoted titles and escaped quotes are parsed correctly. + intact. """ - if not toml_text.strip(): - return None, None - title = tomllib.loads(toml_text).get("title") + title = meta.get("title") if not isinstance(title, str) or not title.strip(): return None, None main, sep, status = title.rpartition(" - ") @@ -141,9 +151,11 @@ def _process_body(lines: list[str]) -> list[str]: def convert(text: str) -> str: - lines = text.splitlines() - toml_text, start = _split_front_matter(lines) - title, status = _title_and_status(toml_text) + # An editor may save the file with a UTF-8 BOM; it must not hide the + # front-matter delimiter (str.strip() does not remove U+FEFF). + lines = text.removeprefix("\ufeff").splitlines() + front, start = _split_front_matter(lines) + title, status = _title_and_status(front) body = "\n".join(_process_body(lines[start:])) + "\n" @@ -159,4 +171,7 @@ def convert(text: str) -> str: if __name__ == "__main__": - sys.stdout.write(convert(sys.stdin.read())) + try: + sys.stdout.write(convert(sys.stdin.read())) + except ValueError as e: + raise SystemExit(f"error: {e}") From 2c43a9767e534741edde60f3d96acb42de93bb0e Mon Sep 17 00:00:00 2001 From: Joseph Heenan Date: Tue, 7 Jul 2026 17:20:28 +0100 Subject: [PATCH 09/13] fix(tools): end the abstract drop at setext headings too MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The abstract-dropping state only terminated on ATX headings ('#'/'.#') or part markers, so a setext-style heading after .# Abstract Foreword ======== was swallowed together with its entire section body — whole sections silently vanished from the Word document. Look one line ahead for a =/- underline under a non-blank line and end the drop there. Co-Authored-By: Claude Fable 5 --- tests/test_mmark_to_pandoc.py | 7 +++++++ tools/mmark-to-pandoc.py | 18 +++++++++++++++--- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/tests/test_mmark_to_pandoc.py b/tests/test_mmark_to_pandoc.py index de0a4a6..72b561d 100644 --- a/tests/test_mmark_to_pandoc.py +++ b/tests/test_mmark_to_pandoc.py @@ -73,6 +73,13 @@ '%%%\ntitle = "T - S"\ndescription = """\n%%%\n"""\n%%%\n\n# Scope\n', '---\ntitle: "T"\nsubtitle: "S"\n---\n\n# Scope\n', ), + ( + "a setext heading terminates the abstract drop " + "(mmark ends the abstract at the next heading of any style)", + ".# Abstract\n\nAbstract text.\n\nForeword\n========\n\n" + "Foreword body.\n\n# Scope\n", + "Foreword\n========\n\nForeword body.\n\n# Scope\n", + ), ] diff --git a/tools/mmark-to-pandoc.py b/tools/mmark-to-pandoc.py index 30341de..4118033 100644 --- a/tools/mmark-to-pandoc.py +++ b/tools/mmark-to-pandoc.py @@ -43,6 +43,7 @@ PART_MARKER = re.compile(r"^\{(?:frontmatter|mainmatter|backmatter)\}\s*$") ABSTRACT_START = re.compile(r"^\.#\s+Abstract\b") IAL_LINE = re.compile(r"^\{:.*\}\s*$") # kramdown inline attribute list +SETEXT_UNDERLINE = re.compile(r"^ {0,3}(=+|-+)\s*$") # A code-fence line (CommonMark rules, which mmark inherits): three or more # backticks/tildes indented at most 3 spaces — deeper indentation makes the # line indented-code *content*, not a fence. group(2) is the info string. @@ -99,7 +100,7 @@ def _process_body(lines: list[str]) -> list[str]: fence = "" dropping_abstract = False - for line in lines: + for i, line in enumerate(lines): m = FENCE.match(line) if m: marker, info = m.group(1), m.group(2) @@ -126,9 +127,20 @@ def _process_body(lines: list[str]) -> list[str]: continue # The abstract runs until the next heading (any level) or part marker; - # everything in between is dropped from the ISO Word output. + # everything in between is dropped from the ISO Word output. Headings + # may also be setext-style: text underlined with = or -. Approximation: + # we end the drop at the line right before the underline, whereas a + # multi-line setext paragraph would make mmark end it at the + # paragraph's first line — close enough for a filter, and it keeps + # whole sections from vanishing. if dropping_abstract: - if HEADING.match(line) or PART_MARKER.match(line): + next_line = lines[i + 1] if i + 1 < len(lines) else "" + starts_setext = ( + line.strip() != "" + and not IAL_LINE.match(line) + and SETEXT_UNDERLINE.match(next_line) + ) + if HEADING.match(line) or PART_MARKER.match(line) or starts_setext: dropping_abstract = False # fall through and handle this line else: continue From b91405945b59f09417c8c8ea57b1843fb970d0b0 Mon Sep 17 00:00:00 2001 From: Joseph Heenan Date: Tue, 7 Jul 2026 18:02:59 +0100 Subject: [PATCH 10/13] fix(tools): drop indented '.# Abstract' and '{: ...}' lines too MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ABSTRACT_START and IAL_LINE were anchored at column 0 while the heading regexes tolerate up to 3 leading spaces (per CommonMark/kramdown). An abstract heading indented 1-3 spaces was therefore rewritten to a literal '# Abstract' clause — shipping the abstract into an ISO deliverable that must not have one — and an indented attribute-list line (e.g. attached to a list item) rendered as literal '{: ...}' body text in the Word document. Co-Authored-By: Claude Fable 5 --- tests/test_mmark_to_pandoc.py | 12 ++++++++++++ tools/mmark-to-pandoc.py | 5 +++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/tests/test_mmark_to_pandoc.py b/tests/test_mmark_to_pandoc.py index 72b561d..ce0b6b2 100644 --- a/tests/test_mmark_to_pandoc.py +++ b/tests/test_mmark_to_pandoc.py @@ -80,6 +80,18 @@ "Foreword body.\n\n# Scope\n", "Foreword\n========\n\nForeword body.\n\n# Scope\n", ), + ( + "an abstract heading indented 1-3 spaces (still a heading) is " + "dropped, not rewritten to a literal '# Abstract' clause", + " .# Abstract\n\nAbstract text.\n\n# Scope\n", + "# Scope\n", + ), + ( + "an attribute list indented 1-3 spaces is dropped, not passed " + "through as literal text", + "# Scope\n\n- item\n {: .keep-with-next}\n\nAfter text.\n", + "# Scope\n\n- item\n\nAfter text.\n", + ), ] diff --git a/tools/mmark-to-pandoc.py b/tools/mmark-to-pandoc.py index 4118033..25f7fc9 100644 --- a/tools/mmark-to-pandoc.py +++ b/tools/mmark-to-pandoc.py @@ -41,8 +41,9 @@ # An ATX heading, including the mmark special-heading ".#" form. HEADING = re.compile(r"^\s{0,3}\.?#{1,6}(?:\s|$)") PART_MARKER = re.compile(r"^\{(?:frontmatter|mainmatter|backmatter)\}\s*$") -ABSTRACT_START = re.compile(r"^\.#\s+Abstract\b") -IAL_LINE = re.compile(r"^\{:.*\}\s*$") # kramdown inline attribute list +# Like headings, these may be indented up to 3 spaces and still count. +ABSTRACT_START = re.compile(r"^ {0,3}\.#\s+Abstract\b") +IAL_LINE = re.compile(r"^ {0,3}\{:.*\}\s*$") # kramdown inline attribute list SETEXT_UNDERLINE = re.compile(r"^ {0,3}(=+|-+)\s*$") # A code-fence line (CommonMark rules, which mmark inherits): three or more # backticks/tildes indented at most 3 spaces — deeper indentation makes the From 19f1b8ad0fa184e4f621d18d309cec8e94244ec3 Mon Sep 17 00:00:00 2001 From: Joseph Heenan Date: Tue, 7 Jul 2026 18:04:20 +0100 Subject: [PATCH 11/13] fix(tools): split converter input on newlines only MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit convert() used str.splitlines(), which also breaks lines at U+2028, U+0085, form feed, and friends — characters markdown (and mmark) treat as ordinary text. A LINE SEPARATOR pasted into prose from Word or a PDF (a common artifact when copying from ISO documents) inside a heading or table row therefore split that line in two in the Word rendition only, silently diverging from the HTML rendition. Normalise CRLF/CR and split on "\n" alone. Co-Authored-By: Claude Fable 5 --- tests/test_mmark_to_pandoc.py | 11 +++++++++++ tools/mmark-to-pandoc.py | 8 +++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/tests/test_mmark_to_pandoc.py b/tests/test_mmark_to_pandoc.py index ce0b6b2..42dbcbe 100644 --- a/tests/test_mmark_to_pandoc.py +++ b/tests/test_mmark_to_pandoc.py @@ -92,6 +92,17 @@ "# Scope\n\n- item\n {: .keep-with-next}\n\nAfter text.\n", "# Scope\n\n- item\n\nAfter text.\n", ), + ( + "unicode line boundaries (U+2028, form feed) pasted from Word/PDF " + "are ordinary characters to markdown, not line breaks", + "# Sco\u2028pe\n\nBody\fmore.\n", + "# Sco\u2028pe\n\nBody\fmore.\n", + ), + ( + "CRLF line endings still split correctly", + "# Scope\r\n\r\n{mainmatter}\r\nBody.\r\n", + "# Scope\n\nBody.\n", + ), ] diff --git a/tools/mmark-to-pandoc.py b/tools/mmark-to-pandoc.py index 25f7fc9..476e638 100644 --- a/tools/mmark-to-pandoc.py +++ b/tools/mmark-to-pandoc.py @@ -166,7 +166,13 @@ def _process_body(lines: list[str]) -> list[str]: def convert(text: str) -> str: # An editor may save the file with a UTF-8 BOM; it must not hide the # front-matter delimiter (str.strip() does not remove U+FEFF). - lines = text.removeprefix("\ufeff").splitlines() + text = text.removeprefix("\ufeff") + # Split on real newlines only: str.splitlines() also breaks on U+2028, + # U+0085, form feed, etc., which markdown treats as ordinary characters \u2014 + # rejoining with "\n" would turn them into hard line breaks. + lines = text.replace("\r\n", "\n").replace("\r", "\n").split("\n") + if lines and lines[-1] == "": + lines.pop() # a trailing newline terminates the last line front, start = _split_front_matter(lines) title, status = _title_and_status(front) From 465a9948e95b496b32a852acb56f30fbdfa1c8ca Mon Sep 17 00:00:00 2001 From: Joseph Heenan Date: Tue, 7 Jul 2026 18:30:22 +0100 Subject: [PATCH 12/13] fix(template): remove the ISO source template; guard the committed docx MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit template/Word_template_for_ISO_standards.dotx carried everything the derived reference docx scrubs: the template author's personal metadata (docProps/core.xml), an "ISO - Internal" classification label (docProps/custom.xml), the full ISO copyright/title-page/footer boilerplate, the ISO logo/OLE/customXml parts, and [trash]/ zip junk — and it is ISO-copyrighted material in a public repo. Sanitizing the committed iso-reference.docx while publishing its dirty source defeated the point. Drop the .dotx; the committed iso-reference.docx remains the build input. To regenerate it, obtain the template from ISO/IEC JTC 1/SC 17 WG 10 and place it at the documented path (the generator now writes to the committed location by default — the old default, build/, was read by nothing). Extend tests/test_iso_reference.py to guard the committed artifacts: the hygiene checks scan every committed .docx/.dotx (via git ls-files, with a new "ISO - Internal" tripwire), and a sync check rebuilds the reference document in memory and compares it part-for-part (decompressed, so zlib differences between environments cannot cause false failures) so a template or generator change cannot silently ship a stale docx — it runs whenever the ISO template is present locally and skips otherwise. Co-Authored-By: Claude Fable 5 --- Makefile | 2 +- template/Word_template_for_ISO_standards.dotx | Bin 89499 -> 0 bytes tests/test_iso_reference.py | 96 ++++++++++++++++-- tools/make-iso-reference.py | 38 +++++-- 4 files changed, 113 insertions(+), 23 deletions(-) delete mode 100644 template/Word_template_for_ISO_standards.dotx diff --git a/Makefile b/Makefile index 87ecd2a..c9e77c6 100644 --- a/Makefile +++ b/Makefile @@ -31,7 +31,7 @@ MD2RFC_IMAGE := danielfett/markdown2rfc@sha256:7b4412559d6ba5db45a14174a28da5b24 # Reference document with the ISO styles/layout, committed to the repo (derived # once from the ISO template by tools/make-iso-reference.py; not regenerated per -# build). +# build). The ISO template itself is not committed — see that script's docstring. REFDOC := template/iso-reference.docx HTML_OUT := $(BUILD)/$(DOC)-editors-copy.html diff --git a/template/Word_template_for_ISO_standards.dotx b/template/Word_template_for_ISO_standards.dotx deleted file mode 100644 index 06754622bc482b3fcd8c8a964d0b459377c8589e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 89499 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

^+;A}H)%BvLa8pEHN^&J7n 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 K<$(JdApZv!jW)6X diff --git a/tests/test_iso_reference.py b/tests/test_iso_reference.py index 5018516..ee03714 100644 --- a/tests/test_iso_reference.py +++ b/tests/test_iso_reference.py @@ -1,24 +1,38 @@ #!/usr/bin/env python3 -"""Hygiene tests for the committed pandoc reference document. +"""Hygiene tests for committed Word (WordprocessingML) files. ``template/iso-reference.docx`` is derived from the ISO Word template by ``tools/make-iso-reference.py`` and committed to the repo (the build uses it directly; the generator is not part of the build). The tool promises that no ISO branding, copyright/IPR boilerplate, or document metadata from the ISO -template travels into the public repo. A regression here is silent — the file -is binary and nobody re-opens it on review — so these tests pin those -guarantees against the committed file. +template travels into the public repo. A regression here is silent — the +files are binary and nobody re-opens them on review — so these tests pin +those guarantees against *every* committed .docx/.dotx (the ISO template +itself is deliberately not committed: it is ISO-copyrighted and carries +personal and classification metadata). Run: python3 tests/test_iso_reference.py """ from __future__ import annotations +import importlib.util +import io import pathlib +import subprocess import sys import zipfile ROOT = pathlib.Path(__file__).resolve().parent.parent REFDOC = ROOT / "template" / "iso-reference.docx" +TEMPLATE = ROOT / "template" / "Word_template_for_ISO_standards.dotx" + +# The generator's filename has a hyphen, so load it by path rather than import. +_spec = importlib.util.spec_from_file_location( + "make_iso_reference", ROOT / "tools" / "make-iso-reference.py" +) +assert _spec and _spec.loader +make_iso_reference = importlib.util.module_from_spec(_spec) +_spec.loader.exec_module(make_iso_reference) def check_no_document_properties(z: zipfile.ZipFile) -> list[str]: @@ -37,18 +51,32 @@ def check_no_document_properties(z: zipfile.ZipFile) -> list[str]: return problems -# Tripwire strings from the ISO title page, copyright/IPR notice, and the -# document-number placeholders. Any one of them surviving in any XML part -# means ISO boilerplate leaked into the public file. +# Tripwire strings from the ISO title page, copyright/IPR notice, the +# document-number placeholders, and the ISO classification label. Any one of +# them surviving in any XML part means ISO boilerplate leaked into the +# public file. ISO_BOILERPLATE_MARKERS = ( "© ISO", "All rights reserved", "copyright@iso.org", "ISO copyright office", "#####", + "ISO - Internal", ) +def committed_word_files() -> list[pathlib.Path]: + """Every WordprocessingML package committed to the repo.""" + out = subprocess.run( + ["git", "ls-files", "-z", "--", "*.docx", "*.dotx", "*.docm", "*.dotm"], + cwd=ROOT, + capture_output=True, + check=True, + text=True, + ).stdout + return [ROOT / name for name in out.split("\0") if name] + + def check_no_iso_boilerplate(z: zipfile.ZipFile) -> list[str]: """No ISO copyright/IPR or title-page text may remain in any XML part (body, headers, footers, notes); footers carry the draft label instead.""" @@ -78,23 +106,69 @@ def check_section_properties_kept(z: zipfile.ZipFile) -> list[str]: return [] if " lost"] -CHECKS = [ +def check_in_sync_with_generator(z: zipfile.ZipFile) -> list[str]: + """Regenerating from the ISO template must reproduce the committed + reference document part-for-part. Otherwise the ISO template or the + generator changed without `python3 tools/make-iso-reference.py` being + re-run — and the build, which reads only the committed file, would + silently ship the stale styles. (Parts are compared decompressed, so + zlib differences between environments cannot cause false failures.) + + The ISO template is not committed (ISO-copyrighted; carries personal and + classification metadata), so this check runs only where an editor has + placed it locally — regenerate and you get the guard; CI skips it. + """ + if not TEMPLATE.is_file(): + print(f"SKIP: generator sync check ({TEMPLATE.name} not present)") + return [] + fresh = zipfile.ZipFile(io.BytesIO(make_iso_reference.build(TEMPLATE))) + if fresh.namelist() != z.namelist(): + return [ + "part list differs from regeneration " + f"(committed {z.namelist()} vs fresh {fresh.namelist()}); " + "re-run tools/make-iso-reference.py and commit the result" + ] + return [ + f"{name}: differs from regeneration; " + "re-run tools/make-iso-reference.py and commit the result" + for name in fresh.namelist() + if fresh.read(name) != z.read(name) + ] + + +# Hygiene rules applied to every committed Word file. +FILE_CHECKS = [ check_no_document_properties, check_no_iso_boilerplate, check_no_trash_entries, +] + +# Structural/consistency rules for the pandoc reference document itself. +REFDOC_CHECKS = [ check_section_properties_kept, + check_in_sync_with_generator, ] def main() -> int: + problems: list[str] = [] + files = committed_word_files() + if REFDOC not in files: + problems.append(f"{REFDOC.relative_to(ROOT)}: not committed") + for path in files: + with zipfile.ZipFile(path) as z: + for check in FILE_CHECKS: + problems += [ + f"{path.relative_to(ROOT)}: {p}" for p in check(z) + ] with zipfile.ZipFile(REFDOC) as z: - problems = [p for check in CHECKS for p in check(z)] + for check in REFDOC_CHECKS: + problems += [f"{REFDOC.relative_to(ROOT)}: {p}" for p in check(z)] for p in problems: print(f"FAIL: {p}", file=sys.stderr) if problems: - print(f"FAIL: {REFDOC.relative_to(ROOT)} is not clean", file=sys.stderr) return 1 - print("OK: iso-reference.docx hygiene checks pass") + print(f"OK: hygiene checks pass for {len(files)} committed Word file(s)") return 0 diff --git a/tools/make-iso-reference.py b/tools/make-iso-reference.py index 1f01158..bc08e9a 100644 --- a/tools/make-iso-reference.py +++ b/tools/make-iso-reference.py @@ -25,7 +25,15 @@ This script is NOT run on every build. Its output is committed as ``template/iso-reference.docx`` and used directly by ``make docx``; run this -script by hand only to regenerate that file when the ISO template changes. +script by hand to regenerate that file whenever the ISO template (or this +script) changes. The ISO template itself is deliberately NOT committed — it is +ISO-copyrighted and carries personal and classification metadata (docProps) — +so obtain it from ISO/IEC JTC 1/SC 17 WG 10 and place it at +``template/Word_template_for_ISO_standards.dotx`` (or pass its path) to +regenerate. ``tests/test_iso_reference.py`` checks the committed file matches +what this script produces whenever that template is present locally, and +always checks that no ISO metadata/boilerplate ships in any committed Word +file. Note: pandoc derives a single-section layout from the reference document, so the committed ``iso-reference.docx`` may still benefit from a one-time manual pass in Word to finalise section setup, page-numbering restarts, and margins. @@ -36,10 +44,11 @@ Usage: python3 tools/make-iso-reference.py [INPUT.dotx] [OUTPUT.docx] -Defaults: template/Word_template_for_ISO_standards.dotx -> build/iso-reference.docx +Defaults: template/Word_template_for_ISO_standards.dotx -> template/iso-reference.docx """ from __future__ import annotations +import io import re import sys import zipfile @@ -176,24 +185,31 @@ def transform(name: str, data: bytes) -> bytes: return data +def build(src: Path) -> bytes: + """Return the transformed reference document as .docx bytes.""" + buf = io.BytesIO() + with zipfile.ZipFile(src) as zin, zipfile.ZipFile( + buf, "w", zipfile.ZIP_DEFLATED + ) as zout: + for item in zin.infolist(): + if _is_dropped(item.filename): + continue + data = transform(item.filename, zin.read(item.filename)) + zout.writestr(item, data) + return buf.getvalue() + + def main() -> int: root = Path(__file__).resolve().parent.parent src = Path(sys.argv[1]) if len(sys.argv) > 1 else root / "template" / "Word_template_for_ISO_standards.dotx" - dst = Path(sys.argv[2]) if len(sys.argv) > 2 else root / "build" / "iso-reference.docx" + dst = Path(sys.argv[2]) if len(sys.argv) > 2 else root / "template" / "iso-reference.docx" if not src.is_file(): print(f"error: template not found: {src}", file=sys.stderr) return 1 dst.parent.mkdir(parents=True, exist_ok=True) - with zipfile.ZipFile(src) as zin, zipfile.ZipFile( - dst, "w", zipfile.ZIP_DEFLATED - ) as zout: - for item in zin.infolist(): - if _is_dropped(item.filename): - continue - data = transform(item.filename, zin.read(item.filename)) - zout.writestr(item, data) + dst.write_bytes(build(src)) try: shown = dst.relative_to(root) From 4813589ebefd4cf80b0be0151731bf9189dcf4e5 Mon Sep 17 00:00:00 2001 From: Joseph Heenan Date: Tue, 7 Jul 2026 18:39:07 +0100 Subject: [PATCH 13/13] chore: cleanup pass over the build/test pipeline - Makefile: add a `make test` target (globs tests/test_*.py, so a new test runs in CI without editing the workflow) and share the Python-with-tomllib guard between docx and test via a need-python prerequisite; build.yml now runs `make test` like the other steps delegate to make. - mmark-to-pandoc.py: spell the 3-space block-indent tolerance the same way in all seven regexes; this also fixes the last members of the indented-construct leak class (an indented {mainmatter} passed through as literal Word text, tab-indented headings ended the abstract drop). Test case added. - make-iso-reference.py: derive _DANGLING from DROP_PREFIXES so the drop list and the reference-stripping list cannot drift (verified output-identical against the ISO template); inline the trivial _is_dropped wrapper. - tests: read each zip part once in the boilerplate scan instead of once per marker; open the reference docx once in main(); drop the redundant name parameter threaded into diff headers; replace repeated why-the-template-is-not-committed prose with pointers to the generator docstring. Co-Authored-By: Claude Fable 5 --- .github/workflows/build.yml | 4 +--- Makefile | 20 ++++++++++++++------ tests/test_iso_reference.py | 32 ++++++++++++++++---------------- tests/test_mmark_to_pandoc.py | 14 ++++++++++---- tools/make-iso-reference.py | 19 ++++++------------- tools/mmark-to-pandoc.py | 17 ++++++++--------- 6 files changed, 55 insertions(+), 51 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e236df7..c006a1a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -44,9 +44,7 @@ jobs: python-version: '3.12' - name: Run tests - run: | - python3 tests/test_mmark_to_pandoc.py - python3 tests/test_iso_reference.py + run: make test # ISO-styled Word document (pandoc), for sharing the draft with ISO. # Delivered as a workflow artifact only; not published to the site. diff --git a/Makefile b/Makefile index c9e77c6..6a534c6 100644 --- a/Makefile +++ b/Makefile @@ -3,6 +3,7 @@ # make html - render the HTML Editor's Copy with markdown2rfc (Docker) # make docx - render the ISO-styled Word document with pandoc # make all - build both +# make test - run the test suite # make clean - remove the build/ directory # # Source scripts live in tools/; everything generated goes to build/ (which is @@ -36,10 +37,17 @@ REFDOC := template/iso-reference.docx HTML_OUT := $(BUILD)/$(DOC)-editors-copy.html -.PHONY: all html docx clean +.PHONY: all html docx test clean need-python all: html docx +# The converter and the tests need $(PYTHON); fail with guidance if none found. +need-python: + @test -n "$(strip $(PYTHON))" || { \ + echo "error: no Python with tomllib (3.11+) found;"; \ + echo " install one or run: make PYTHON=/path/to/python3.11+"; \ + exit 1; } + ## HTML Editor's Copy (markdown2rfc / mmark) -> build/ html: $(SRC) # Clean stale intermediates first so the copy below is unambiguous even if a @@ -54,11 +62,7 @@ html: $(SRC) @echo "HTML Editor's Copy -> $(HTML_OUT)" ## ISO-styled Word document (pandoc) -> build/ -docx: $(SRC) $(REFDOC) $(TOOLS)/mmark-to-pandoc.py $(TOOLS)/iso-styles.lua - @test -n "$(strip $(PYTHON))" || { \ - echo "error: no Python with tomllib (3.11+) found;"; \ - echo " install one or run: make docx PYTHON=/path/to/python3.11+"; \ - exit 1; } +docx: need-python $(SRC) $(REFDOC) $(TOOLS)/mmark-to-pandoc.py $(TOOLS)/iso-styles.lua mkdir -p $(BUILD) $(PYTHON) $(TOOLS)/mmark-to-pandoc.py < $(SRC) > $(BUILD)/$(DOC).pandoc.md pandoc $(BUILD)/$(DOC).pandoc.md \ @@ -68,6 +72,10 @@ docx: $(SRC) $(REFDOC) $(TOOLS)/mmark-to-pandoc.py $(TOOLS)/iso-styles.lua rm -f $(BUILD)/$(DOC).pandoc.md @echo "ISO Word document -> $(BUILD)/$(DOC).docx" +## Test suite (every tests/test_*.py, so new tests run without editing this) +test: need-python + @for t in tests/test_*.py; do $(PYTHON) $$t || exit 1; done + clean: rm -rf $(BUILD) rm -f $(SRCDIR)/$(DOC)*.html $(SRCDIR)/$(DOC)*.xml $(SRCDIR)/$(DOC)*.txt diff --git a/tests/test_iso_reference.py b/tests/test_iso_reference.py index ee03714..9631a2f 100644 --- a/tests/test_iso_reference.py +++ b/tests/test_iso_reference.py @@ -8,8 +8,7 @@ template travels into the public repo. A regression here is silent — the files are binary and nobody re-opens them on review — so these tests pin those guarantees against *every* committed .docx/.dotx (the ISO template -itself is deliberately not committed: it is ISO-copyrighted and carries -personal and classification metadata). +itself is deliberately not committed — see tools/make-iso-reference.py). Run: python3 tests/test_iso_reference.py """ @@ -80,13 +79,17 @@ def committed_word_files() -> list[pathlib.Path]: def check_no_iso_boilerplate(z: zipfile.ZipFile) -> list[str]: """No ISO copyright/IPR or title-page text may remain in any XML part (body, headers, footers, notes); footers carry the draft label instead.""" - return [ - f"{name}: contains {marker!r}" - for name in z.namelist() - if name.endswith(".xml") - for marker in ISO_BOILERPLATE_MARKERS - if marker in z.read(name).decode("utf-8") - ] + problems = [] + for name in z.namelist(): + if not name.endswith(".xml"): + continue + text = z.read(name).decode("utf-8") + problems += [ + f"{name}: contains {marker!r}" + for marker in ISO_BOILERPLATE_MARKERS + if marker in text + ] + return problems def check_no_trash_entries(z: zipfile.ZipFile) -> list[str]: @@ -114,9 +117,8 @@ def check_in_sync_with_generator(z: zipfile.ZipFile) -> list[str]: silently ship the stale styles. (Parts are compared decompressed, so zlib differences between environments cannot cause false failures.) - The ISO template is not committed (ISO-copyrighted; carries personal and - classification metadata), so this check runs only where an editor has - placed it locally — regenerate and you get the guard; CI skips it. + The ISO template is not committed (see tools/make-iso-reference.py), so + this check runs only where an editor has placed it locally; CI skips it. """ if not TEMPLATE.is_file(): print(f"SKIP: generator sync check ({TEMPLATE.name} not present)") @@ -156,14 +158,12 @@ def main() -> int: if REFDOC not in files: problems.append(f"{REFDOC.relative_to(ROOT)}: not committed") for path in files: + checks = FILE_CHECKS + (REFDOC_CHECKS if path == REFDOC else []) with zipfile.ZipFile(path) as z: - for check in FILE_CHECKS: + for check in checks: problems += [ f"{path.relative_to(ROOT)}: {p}" for p in check(z) ] - with zipfile.ZipFile(REFDOC) as z: - for check in REFDOC_CHECKS: - problems += [f"{REFDOC.relative_to(ROOT)}: {p}" for p in check(z)] for p in problems: print(f"FAIL: {p}", file=sys.stderr) if problems: diff --git a/tests/test_mmark_to_pandoc.py b/tests/test_mmark_to_pandoc.py index 42dbcbe..fc66af2 100644 --- a/tests/test_mmark_to_pandoc.py +++ b/tests/test_mmark_to_pandoc.py @@ -92,6 +92,12 @@ "# Scope\n\n- item\n {: .keep-with-next}\n\nAfter text.\n", "# Scope\n\n- item\n\nAfter text.\n", ), + ( + "a part marker indented 1-3 spaces is dropped, not passed through " + "as literal text", + "# Scope\n\n {mainmatter}\n\nAfter text.\n", + "# Scope\n\n\nAfter text.\n", + ), ( "unicode line boundaries (U+2028, form feed) pasted from Word/PDF " "are ordinary characters to markdown, not line breaks", @@ -113,12 +119,12 @@ def _convert_or_error(source: str) -> str: return f"\n" -def _diff(name: str, expected: str, got: str) -> str: +def _diff(expected: str, got: str) -> str: return "".join( difflib.unified_diff( expected.splitlines(keepends=True), got.splitlines(keepends=True), - fromfile=f"expected [{name}]", + fromfile="expected", tofile="got", ) ) @@ -129,13 +135,13 @@ def check_golden() -> list[str]: expected = (FIXTURES / "sample.expected.md").read_text() got = mmark_to_pandoc.convert(source) if got != expected: - return ["golden output mismatch:\n" + _diff("sample.md", expected, got)] + return ["golden output mismatch:\n" + _diff(expected, got)] return [] def check_cases() -> list[str]: return [ - f"{name}:\n" + _diff(name, expected, got) + f"{name}:\n" + _diff(expected, got) for name, source, expected in CASES if (got := _convert_or_error(source)) != expected ] diff --git a/tools/make-iso-reference.py b/tools/make-iso-reference.py index bc08e9a..20450f4 100644 --- a/tools/make-iso-reference.py +++ b/tools/make-iso-reference.py @@ -98,20 +98,13 @@ def repl(m: "re.Match[str]") -> str: # edited the ISO template; it is not part of the OPC package. DROP_PREFIXES = ("word/media/", "word/embeddings/", "customXml/", "docProps/", "[trash]/") - -def _is_dropped(name: str) -> bool: - return name.startswith(DROP_PREFIXES) - - # Substrings that identify a relationship/content-type entry pointing at a # dropped part, so we can keep [Content_Types].xml and the *.rels files -# internally consistent after the drop. -_DANGLING = ( - "media/", - "embeddings/", - "customXml/", - "docProps/", -) +# internally consistent after the drop. Derived from DROP_PREFIXES because +# the two must move in lockstep; relationship targets are relative to the +# referencing part, so "word/media/" appears as "media/" ("[trash]/" never +# appears in a relationship target, matching it is harmless). +_DANGLING = tuple(p.removeprefix("word/") for p in DROP_PREFIXES) _XML_ELEMENT = re.compile(r"<(Relationship|Override|Default)\b[^>]*/>") @@ -192,7 +185,7 @@ def build(src: Path) -> bytes: buf, "w", zipfile.ZIP_DEFLATED ) as zout: for item in zin.infolist(): - if _is_dropped(item.filename): + if item.filename.startswith(DROP_PREFIXES): continue data = transform(item.filename, zin.read(item.filename)) zout.writestr(item, data) diff --git a/tools/mmark-to-pandoc.py b/tools/mmark-to-pandoc.py index 476e638..83faa6c 100644 --- a/tools/mmark-to-pandoc.py +++ b/tools/mmark-to-pandoc.py @@ -38,18 +38,17 @@ import tomli as tomllib # type: ignore[no-redef] -# An ATX heading, including the mmark special-heading ".#" form. -HEADING = re.compile(r"^\s{0,3}\.?#{1,6}(?:\s|$)") -PART_MARKER = re.compile(r"^\{(?:frontmatter|mainmatter|backmatter)\}\s*$") -# Like headings, these may be indented up to 3 spaces and still count. +# All block-level constructs may be indented up to 3 *spaces* and still count +# (CommonMark rules, which mmark inherits): 4+ spaces — or a tab, which counts +# as 4 columns — make the line indented-code content instead. Every regex +# below spells that tolerance the same way: `^ {0,3}`. +HEADING = re.compile(r"^ {0,3}\.?#{1,6}(?:\s|$)") # ATX, incl. mmark's ".#" +PART_MARKER = re.compile(r"^ {0,3}\{(?:frontmatter|mainmatter|backmatter)\}\s*$") ABSTRACT_START = re.compile(r"^ {0,3}\.#\s+Abstract\b") IAL_LINE = re.compile(r"^ {0,3}\{:.*\}\s*$") # kramdown inline attribute list SETEXT_UNDERLINE = re.compile(r"^ {0,3}(=+|-+)\s*$") -# A code-fence line (CommonMark rules, which mmark inherits): three or more -# backticks/tildes indented at most 3 spaces — deeper indentation makes the -# line indented-code *content*, not a fence. group(2) is the info string. -FENCE = re.compile(r"^ {0,3}(`{3,}|~{3,})(.*)$") -SPECIAL_HEADING = re.compile(r"^(\s{0,3})\.(#{1,6})") +FENCE = re.compile(r"^ {0,3}(`{3,}|~{3,})(.*)$") # group(2) is the info string +SPECIAL_HEADING = re.compile(r"^( {0,3})\.(#{1,6})") def _split_front_matter(lines: list[str]) -> tuple[dict, int]: