From 3940b2f32930e106a912830892dc3097be204c0f Mon Sep 17 00:00:00 2001
From: airmang <38392618+airmang@users.noreply.github.com>
Date: Tue, 21 Jul 2026 04:00:53 +0900
Subject: [PATCH 1/3] =?UTF-8?q?docs:=20minimal=20README=20=E2=80=94=20one?=
=?UTF-8?q?=20proof=20line,=20one=20code=20sample,=20links=20as=20the=20so?=
=?UTF-8?q?urce=20of=20detail?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Co-Authored-By: Claude Fable 5
- 한컴 없이 HWPX를 안전하게 자동화하는 Python 계층 — 최소 범위 편집, 검증된 저작, 모든 쓰기에 영수증.
+ 한컴 없이 HWPX를 읽고, 고치고, 만드는 순수 파이썬 라이브러리
python-hwpx
한국어 | English
---- - -> **python-hwpx는 한컴 없이 HWPX를 안전하게 자동화하는 Python 계층입니다.** 기존 -> 문서는 최소 범위만 수정하고, 새 문서는 실제 한컴 수용이 검증된 형태로 생성하며, -> 모든 쓰기에 변경·보존·검증 영수증을 남기고, 완전한 해석과 렌더링은 전문 백엔드에 -> 위임할 수 있습니다. - -- **최소 범위 편집** — 미수정 part는 저장 시 바이트 그대로 유지됩니다(patch 경로 - 바이트 보존 497/497, 동결 코퍼스 v2 · 2026-07-19). -- **검증된 저작** — 밑바닥 생성도 실제 한컴이 받아들이는 형태로 냅니다(산출물 한컴 - 오픈 476/476 all-pass, 실저작 품질 게이트 58/58). -- **모든 쓰기에 영수증** — 대표 저장 경로는 [Safe Write Contract](docs/safe-write-contract.md)의 - `MutationReport`(`hwpx.mutation-report/v1`)로 실제 쓰기 모드·보존 등급·검증 결과를 - **측정해** 반환합니다. +기존 문서는 손댄 곳만 고치고(미수정 영역은 바이트 그대로), 새 문서는 실제 +한컴오피스가 받아들이는 형태로 만듭니다. 산출물은 실제 한컴으로 전수 측정해 +그대로 공개합니다 — [실측 코퍼스 메트릭](https://airmang.github.io/python-hwpx/corpus-metrics.html). ---- - -## 🧩 HWPX Stack (3종) - -| 계층 | 레포 | 역할 | +| | 레포 | 역할 | |---|---|---| -| 📦 라이브러리 | **[`python-hwpx`](https://github.com/airmang/python-hwpx)** | 순수 파이썬 HWPX 파싱·편집·생성 코어 | -| 🔌 MCP 서버 | [`hwpx-mcp-server`](https://github.com/airmang/hwpx-mcp-server) | MCP 클라이언트(Claude Desktop, VS Code 등)에서 HWPX 조작 | -| 🎯 에이전트 스킬 | [`hwpx-plugin`](https://github.com/airmang/hwpx-plugins) | 에이전트가 HWPX를 바로 쓰게 해주는 first-party 플러그인·스킬 번들 | - -`hwpx-mcp-server`와 `hwpx-plugin`은 같은 프로젝트가 직접 유지보수하는 first-party 연동 -구성요소입니다(유지보수 관계를 뜻하며, 한컴 등 외부 기관의 인증을 뜻하지 않습니다). - -현재 PyPI 공개 릴리스는 `python-hwpx 3.8.0`입니다. 일반 -`pip install python-hwpx`로 이 릴리스를 설치할 수 있습니다. -현재 패키지 분류는 `Development Status :: 3 - Alpha`입니다. 이 분류는 API와 제품의 -성숙도를 나타내며, 공개 버전이나 플러그인의 최소 호환 버전을 대신하지 않습니다. - ---- - -## 실측으로 말합니다 — Published Corpus - -이 스택의 산출물은 주장 대신 **실제 한컴오피스 전수 측정**으로 검증됩니다 -(동결 코퍼스 v2, N=497 산출물, 2026-07-19, 실한컴 12.0.0.3288 COM/GUI 오라클; -상세·주의사항은 -[실측 코퍼스 메트릭](https://airmang.github.io/python-hwpx/corpus-metrics.html)): - -- **한컴 오픈 수용률 476/476 all-pass** (동결 코퍼스 v2 · 2026-07-19 · 실한컴 COM - `Open()` 판정 · rule-of-three 하한 99.37%) · 파싱 96.2%(458/476) -- **미수정 영역 바이트 보존 497/497** (patch 경로 한정, zip-part diff · 오라클 불요) - · **개인정보 0-leak** (35문서/합성 140값) -- 렌더 검증 416/476 (실한컴 `SaveAs("PDF")`) + 정직 버킷 43건(변경추적 문서의 PDF - export는 한컴 자체가 거부 — 실측 한계로 발행) + 미검증 17건 -- wild 공개 양식 채움은 구조결함 픽스 후 **무음 서식파괴 16.7%**(판정 66조합, 못 담는 타깃은 typed 거부 35건·산출분 pass 17/28) — **낮은 숫자도 그대로 발행**하고 잔여(페이지 리플·표 shape)를 명명합니다 - -
-
-
python-hwpx로 작성한 변경추적(취소선·삽입)과 AI 에이전트 메모 — 실제 한/글에서 연 화면입니다.
- -> 이 숫자들은 *생성물 수용률* 축입니다(우리가 만든 파일을 실제 한컴이 받아들이는가). -> 문서 *파싱 recall*과는 다른 축이므로 파서 프로젝트 수치와 병치 비교하지 마세요. +| 📦 | **`python-hwpx`** | 순수 파이썬 HWPX 코어 (이 레포) | +| 🔌 | [`hwpx-mcp-server`](https://github.com/airmang/hwpx-mcp-server) | MCP 클라이언트(Claude Desktop 등)에서 HWPX 조작 | +| 🎯 | [`hwpx-plugin`](https://github.com/airmang/hwpx-plugins) | 에이전트용 플러그인·스킬 번들 | ---- - -## 왜 python-hwpx인가 - -- **코어 편집에 한컴오피스 설치 불필요** — HWPX는 ZIP+XML(OWPML/OPC) 구조라, 순수 파이썬으로 Windows·macOS·Linux·CI 어디서나 읽고 씁니다. -- **읽기부터 생성까지 한 코어** — 텍스트/서식 추출, 문단·표·양식 편집, 새 문서 생성, XSD 스키마 검증을 하나의 API로 처리합니다. -- **에이전트·자동화 친화** — 같은 프로젝트가 유지보수하는 `hwpx-mcp-server`와 `hwpx-plugin`이 코어에 연결됩니다. - -문서 파싱·편집·생성은 순수 Python으로 수행할 수 있습니다. 다만 페이지 나눔, 표 넘침, -글꼴 대체 등 최종 시각 품질을 확언하려면 필요에 따라 실제 한컴 렌더 오라클을 별도로 사용합니다. - -## 빠른 시작 +## 시작하기 ```bash -pip install python-hwpx # Python 3.10+ · lxml ≥ 4.9 +pip install python-hwpx # Python 3.10+ ``` ```python from hwpx import HwpxDocument -# 기존 문서 열기 → 편집 → 저장 doc = HwpxDocument.open("보고서.hwpx") doc.add_paragraph("자동화로 추가한 문단입니다.") doc.save_to_path("보고서-수정.hwpx") - -# 새 문서 만들기 -new = HwpxDocument.new() -new.add_paragraph("python-hwpx로 만든 새 문서") -new.save_to_path("새문서.hwpx") ``` -> 💡 컨텍스트 매니저도 지원합니다 — `with` 블록을 벗어나면 리소스가 자동 정리됩니다: -> ```python -> with HwpxDocument.open("보고서.hwpx") as doc: -> doc.add_paragraph("자동으로 리소스가 정리됩니다.") -> doc.save_to_path("결과물.hwpx") -> ``` - -`open`/`new` → `edit`/`extract` → `save_to_path` 흐름만 잡으면 나머지는 필요할 때 확장하면 됩니다. - ## 무엇을 하나 -### 🔍 읽기 · 추출 -- 텍스트/HTML/Markdown 내보내기 — `export_text()` · `export_html()` · `export_markdown()` -- **풍부한 Markdown** — `export_rich_markdown()`은 인라인 서식(`**굵게**`·`*기울임*`·`~~취소선~~`), 중첩 표(colspan/rowspan 안전), 도형 텍스트, 이미지, 각주/미주, 하이퍼링크, 제목(`#`/`##`) 자동 감지까지 보존 -- **문서 ingest 게이트웨이** — `hwpx.ingest.DocumentIngestor`가 HWPX를 감지해 rich Markdown과 섹션/표 메타데이터로 정규화 -- `TextExtractor` / `ObjectFinder` — 섹션·문단 순회, 태그·속성·XPath로 객체 탐색 (`hp:tab`은 `\t`로 보존, roundtrip 안전) +- **읽기·추출** — 텍스트/HTML/rich Markdown 내보내기(서식·중첩 표·각주 보존), XPath 객체 탐색 +- **편집** — 문단·표·이미지·머리글/바닥글·메모·각주, 줄간격·여백·쪽번호 등 서식 +- **양식 채우기** — 라벨·경로 기반 셀 채움, 바이트 보존 구조 편집(행·열·오토핏·shrink-to-fit) +- **생성** — 조립형 builder, 공문 lint·결재란, 사진대지·명패·조직도, mail merge, 신구대조표 +- **변경추적·목차** — redline 저작, 네이티브 목차·상호참조 +- **검증·안전** — XSD·패키지 검증 CLI, 열림 안전 게이트, 모든 쓰기에 영수증(`MutationReport`) -```python -doc = HwpxDocument.open("보고서.hwpx") -md = doc.export_rich_markdown( - image_dir="out/images", # BinData 이미지를 디스크에 추출 - image_ref_prefix="images/", # 마크다운 내  경로 접두 - detect_headings=True, # Ⅰ./1. 패턴 기반 #/## 자동 -) -``` +자세한 내용: [사용 가이드](docs/usage.md) · [API 레퍼런스](https://airmang.github.io/python-hwpx/) · [예제](docs/examples.md) -### ✏️ 편집 -- 문단 추가/삭제/서식, Run 단위 볼드·이탤릭·밑줄·색상 -- 섹션 추가/삭제(`add_section(after=)`·`remove_section()`, manifest 자동 관리) -- 표 생성·셀 텍스트·병합/분할·중첩 테이블, 이미지 임베드, 머리글/바닥글, 메모(앵커 기반), 각주/미주, 북마크/하이퍼링크, 다단 편집 -- **기존 문서 서식 편집** — 정렬·줄간격·들여쓰기·문단 간격, 용지·여백·방향, 쪽번호, 불릿/번호 -- **스타일 기반 치환** — 색상·밑줄·`charPrIDRef`로 Run을 필터링해 선택 교체(`replace_text_in_runs`·`find_runs_by_style`) +## 신뢰의 근거 -```python -# 빨간색 텍스트만 찾아서 치환 -doc.replace_text_in_runs("임시", "확정", text_color="#FF0000") -``` +- [안전한 쓰기 계약](docs/safe-write-contract.md) — 보존 등급을 쓰기 전에 판정하고, 실제 변경을 측정한 영수증을 반환합니다(fail-closed) +- [지원 매트릭스](docs/support-matrix.md) — 기능별 실제 지원 등급, 안 되는 것도 등급으로 명시 +- [실측 코퍼스 메트릭](https://airmang.github.io/python-hwpx/corpus-metrics.html) — 실한컴 전수 측정 수치와 주의사항, 낮은 숫자도 그대로 발행 -### 🖊️ 양식 채우기 (byte-preserving) -- 누름틀(클릭히어) 필드 조회·서식 보존 채움, 라벨 기반 셀 탐색(`find_cell_by_label`)·경로 채우기(`fill_by_path`) -- **바이트 보존 구조 편집** — 셀 채우기 / 행·열·표 삭제·삽입 / 열 너비 오토핏 / 폰트 shrink-to-fit 을 문서 재조립 없이 수행해 양식 서식을 그대로 보존. 미수정 영역은 `hwpx.patch`가 section XML 바이트를 splice해 손대지 않음 - -```python -doc = HwpxDocument.open("신청서.hwpx") -result = doc.fill_by_path({ - "성명 > right": "홍길동", - "소속 > right": "플랫폼팀", -}) -doc.save_to_path("신청서-작성완료.hwpx") -print(result["applied_count"], result["failed_count"]) -``` +현재 개발 상태는 Alpha입니다 — API는 바뀔 수 있습니다. -### 🏗️ 생성 · 공문서 도구 -- `hwpx.builder` — Section/Heading/Table/Image/Header 조립형 생성 + 하드게이트 저장 리포트 -- 공문서 도구 — `official_lint`(항목기호 위계·"끝." 표시·붙임·날짜 lint), 결재란 프리셋 -- `advanced_generators` — 사진대지(image_grid)·회의 명패·표 기반 조직도 -- `mail_merge` — 템플릿+데이터 N부 대량 생성, 표 합계·평균 계산 -- `doc_diff` — 문단 LCS diff·신구대조표·참조 정합 lint -- `style_profile` — 참조 문서 프로파일 추출·적용, 템플릿 레지스트리 - -### ✅ 검증 · 안전 · 저수준 -- XSD 스키마 + 패키지 구조 검증 — CLI `hwpx-validate` · `hwpx-validate-package`, `hwpx-analyze-template` -- `validate_editor_open_safety` — 저장/팩/리페어/빌더 출력 게이트, `openSafety` 증거 반환 -- `hwpx.tools.fuzz`(시드 결정적 시나리오·3중 오라클) · `hwpx.tools.layout_preview`(페이지 박스 근사 HTML/PNG 자기검증) · `opc.security`(XML entity·ZIP 압축 폭탄 가드) -- `hwpx.oxml` 데이터클래스로 OWPML 스키마 ↔ Python 객체 직접 조작, HWPML 2016→2011 네임스페이스 자동 정규화 - -```bash -hwpx-validate-package 보고서.hwpx -hwpx-analyze-template 보고서.hwpx -``` - -> 전체 기능·클래스·메서드 목록은 [사용 가이드](docs/usage.md)와 [API 레퍼런스](https://airmang.github.io/python-hwpx/api_reference.html)를 참고하세요. - -## 안전한 쓰기 계약 (Safe Write Contract) - -대표 저장 경로(`save_to_path` · `save_to_stream` · `to_bytes`)는 **요청한 보존 등급을 -쓰기 전에 판정하고, 실제로 무엇을 바꿨는지 측정한 영수증**을 돌려줍니다. - -```python -from hwpx.mutation_report import PreservationDowngradeError - -# 영수증과 함께 저장 — 달성 가능한 가장 강한 보존 등급 자동 선택(mode="auto" 기본) -report = doc.save_to_path("결과.hwpx", return_report=True) -print(report.actual_mode) # "patch" | "rebuild" -print(report.preservation.untouched_part_payloads.to_dict()) # {"verified": 17, "changed": 0} - -# patch 등급 강제 — 미달이면 아무것도 쓰지 않고 예외(fail-closed) -try: - doc.save_to_path("결과.hwpx", mode="patch", fallback="error") -except PreservationDowngradeError as exc: - print(exc.offending_parts, exc.suggestion) -``` - -- `mode="patch" | "rebuild" | "auto"`(기본 `auto`) · `fallback="error" | "rebuild"`(기본 `error`) -- `mode="patch"` + `fallback="error"`에서 미수정 part의 바이트 동일성을 지킬 수 없으면 - **아무것도 쓰지 않고** `PreservationDowngradeError`를 던집니다(무음 rebuild 없음). -- `MutationReport`는 `requestedMode`/`actualMode`/`fallbackUsed`, 변경 part와 좌표 명시 - 범위, 보존 3층(part 페이로드·ZIP 레코드·전체 패키지), 검증 3항목(`passed`/`failed`/`not_performed`)을 - **측정해** 반환합니다. - -> 파라미터 전체와 `MutationReport` 스키마는 [안전한 쓰기 계약 문서](docs/safe-write-contract.md)를 참고하세요. - -## 지원 매트릭스 - -능력 영역별 실제 등급입니다(동결 코퍼스 v2 · 2026-07-19 · 실한컴 12.0.0.3288 오라클). -등급 어휘: **Parse / Preserve / Edit / Create / Render-verified / -Unsupported-but-preserved / Unsupported-and-rejected**. - -| 능력 영역 | 상태 | 증거 | -|---|---|---| -| 문단·표 저작/편집 | Parse·Preserve·Edit·Create·Render-verified | 오픈 476/476 · 실저작 게이트 58/58 · 렌더 416 | -| 표 구조 변경(행·열·표, 오토핏) | Preserve·Edit | `hwpx.table_patch` · 바이트 보존 497/497 | -| 양식 채움(byte-splice) | Preserve·Edit | `hwpx.patch`·`table_patch`·`body_patch` · 보존 497/497 (wild 무음 서식파괴 16.7%·typed 거부 35/66, 잔여 명명) | -| 그림 삽입/치환 | Edit·Create | `add_picture`·`replace_picture` (복잡 개체는 한컴 확인 권장) | -| 차트 | Unsupported-but-preserved | 생성 API 없음 · 기존 차트 part는 patch 보존 | -| 수식 | Parse·Unsupported-but-preserved | 저작 API 없음 · 기존 수식 파싱·patch 보존 | -| 변경추적(redline) | Edit·Create | `add_tracked_*` · 실한컴 `IsTrackChange=1` (한컴이 PDF export 거부 → `render_unavailable` 정직 집계) | -| 메모(코멘트) | Edit·Create·Render-verified | `add_memo*` · 실 Windows 한컴 검증 | -| 각주/미주 | Edit·Create | `add_footnote`·`add_endnote` (렌더 독립 게이트 미측정) | -| 네이티브 목차/상호참조 | Create·Render-verified | `add_native_toc`·`toc_verify` · 구조 15/15 · 페이지 정합 5/5 | -| 암호화 HWPX | Unsupported-and-rejected | 복호화 없음 · 암호화 part는 파싱 단계 예외로 거부 | -| HWP 5.x 바이너리 | Unsupported-and-rejected | ZIP 아님 → 열기 시 `BadZipFile` (HWPX로 변환 후 사용) | -| 누름틀(form field) 생성 | Parse·Edit | 기존 필드 조회·서식보존 채움 · **신규 누름틀 생성 도구는 미제공** | - -> 각 등급의 판정 근거와 상세 증거 포인터는 [지원 매트릭스 문서](docs/support-matrix.md)를 참고하세요. - -## 대항 라이브러리 비교 +## 비교 | | python-hwpx | pyhwpx | pyhwp | |---|---|---|---| @@ -238,58 +65,32 @@ Unsupported-but-preserved / Unsupported-and-rejected**. | **한/글 설치** | 불필요 | 필요 (Windows COM) | 불필요 | | **크로스 플랫폼** | ✅ Linux / macOS / Windows / CI | ❌ Windows 전용 | ✅ | | **편집/생성 API** | ✅ | ✅ (COM) | ❌ 대부분 읽기 | -| **스키마 검증** | ✅ | ❌ | ❌ | -| **AI 에이전트 연동 (MCP)** | ✅ `hwpx-mcp-server` | ❌ | ❌ | +| **AI 에이전트 연동 (MCP)** | ✅ | ❌ | ❌ | -> HWP(v5 바이너리) 파일은 지원하지 않습니다. 한컴오피스에서 HWPX로 변환 후 사용하세요. +> HWP(v5 바이너리)는 지원하지 않습니다. 한컴오피스에서 HWPX로 변환 후 사용하세요. ## 알려진 제약 -- `add_shape()` / `add_control()`은 한/글이 요구하는 모든 하위 요소를 생성하지 않습니다. 복잡한 개체 추가 시 한/글에서 열어 검증하세요. -- 이미지 바이너리 임베드는 지원하지만 `
- A Python layer for safely automating HWPX without Hancom — minimal-scope edits, verified authoring, a receipt on every write. + A pure-Python library to read, edit, and create HWPX — no Hancom Office required
한국어 | English
---- - -> **python-hwpx is a Python layer for safely automating HWPX without Hancom.** It -> edits existing documents in the minimal scope, generates new documents in a form -> verified to be accepted by real Hancom, leaves change/preservation/verification -> receipts on every write, and can delegate full interpretation and rendering to -> specialized backends. - -- **Minimal-scope edits** — untouched parts stay byte-for-byte on save (byte - preservation 497/497 on the patch path, frozen corpus v2 · 2026-07-19). -- **Verified authoring** — from-scratch generation lands in a form real Hancom - accepts (produced-output Hancom open 476/476 all-pass, authoring quality gate 58/58). -- **A receipt on every write** — the representative save paths return a - [Safe Write Contract](docs/safe-write-contract.md) `MutationReport` - (`hwpx.mutation-report/v1`) that **measures** the actual write mode, preservation - grade, and verification result. +Existing documents are edited in place — untouched regions stay byte-identical. +New documents are produced in a form real Hancom Office accepts, and every output +is measured against real Hancom and published as-is — +[measured corpus metrics](https://airmang.github.io/python-hwpx/corpus-metrics.html). ---- - -## 🧩 HWPX Stack (3 components) - -| Layer | Repo | Role | +| | Repo | Role | |---|---|---| -| 📦 Library | **[`python-hwpx`](https://github.com/airmang/python-hwpx)** | Pure-Python HWPX parsing / editing / generation core | -| 🔌 MCP server | [`hwpx-mcp-server`](https://github.com/airmang/hwpx-mcp-server) | Manipulate HWPX from MCP clients (Claude Desktop, VS Code, etc.) | -| 🎯 Agent skill | [`hwpx-plugin`](https://github.com/airmang/hwpx-plugins) | First-party plugin / skill bundle that lets agents use HWPX directly | - -`hwpx-mcp-server` and `hwpx-plugin` are first-party integration components maintained -directly by the same project ("first-party" describes the maintenance relationship, -not certification by Hancom or any third party). - -The current public PyPI release is `python-hwpx 3.7.0`. A plain -`pip install python-hwpx` installs this release. -The current package classifier is `Development Status :: 3 - Alpha`. This classifier -reflects the maturity of the API and product; it does not stand in for the public -version or the minimum compatible version of the plugin. - ---- - -## We speak in measurements — Published Corpus - -The outputs of this stack are verified by **exhaustive measurement against real -Hancom Office**, not by claims (frozen corpus v2, N=497 produced outputs, 2026-07-19, -real Hancom 12.0.0.3288 COM/GUI oracle; details and caveats in the -[measured corpus metrics](https://airmang.github.io/python-hwpx/corpus-metrics.html)): - -- **Hancom open-acceptance 476/476 all-pass** (frozen corpus v2 · 2026-07-19 · real - Hancom COM `Open()` · rule-of-three lower bound 99.37%) · parsing 96.2% (458/476) -- **Byte preservation of untouched regions 497/497** (patch path only, zip-part diff · - no oracle needed) · **personal-info 0-leak** (35 docs / 140 synthetic values) -- 416/476 render-verified (real Hancom `SaveAs("PDF")`) + honesty bucket of 43 (PDF - export of tracked-change documents is refused by Hancom itself — a measured - limitation) + 17 unverified -- Wild-form filling after the structural-defect fix: **silent layout breakage 16.7%** (66 judged combos; impossible targets are 35 typed refusals, produced fills pass 17/28) — **we publish the low numbers as-is** and name the residual (page ripple, table shape) - -
-
-
Tracked changes (strikethrough/insertion) and AI-agent margin memos written by python-hwpx — opened in real Hancom Office.
+| 📦 | **`python-hwpx`** | Pure-Python HWPX core (this repo) | +| 🔌 | [`hwpx-mcp-server`](https://github.com/airmang/hwpx-mcp-server) | Drive HWPX from MCP clients (Claude Desktop, etc.) | +| 🎯 | [`hwpx-plugin`](https://github.com/airmang/hwpx-plugins) | Plugin / skill bundle for agents | -> These numbers are on the *output acceptance* axis (does real Hancom accept the files we produce). -> This is a different axis from document *parsing recall*, so do not compare it side by side with parser-project figures. - ---- - -## Why python-hwpx - -- **No Hancom Office required for core editing** — HWPX is a ZIP+XML (OWPML/OPC) structure, so pure Python reads and writes it anywhere: Windows, macOS, Linux, CI. -- **From reading to generation in one core** — text/format extraction, paragraph/table/form editing, new-document generation, and XSD schema validation are all handled by one API. -- **Agent- and automation-friendly** — `hwpx-mcp-server` and `hwpx-plugin`, maintained by the same project, connect to the core. - -Document parsing, editing, and generation can be done in pure Python. However, to -assert final visual quality — page breaks, table overflow, font substitution, and so -on — you separately use a real Hancom render oracle as needed. - -## Quick start +## Getting started ```bash -pip install python-hwpx # Python 3.10+ · lxml ≥ 4.9 +pip install python-hwpx # Python 3.10+ ``` ```python from hwpx import HwpxDocument -# Open an existing document → edit → save -doc = HwpxDocument.open("보고서.hwpx") -doc.add_paragraph("자동화로 추가한 문단입니다.") -doc.save_to_path("보고서-수정.hwpx") - -# Create a new document -new = HwpxDocument.new() -new.add_paragraph("python-hwpx로 만든 새 문서") -new.save_to_path("새문서.hwpx") +doc = HwpxDocument.open("report.hwpx") +doc.add_paragraph("A paragraph added by automation.") +doc.save_to_path("report-edited.hwpx") ``` -> 💡 A context manager is also supported — resources are cleaned up automatically on leaving the `with` block: -> ```python -> with HwpxDocument.open("보고서.hwpx") as doc: -> doc.add_paragraph("자동으로 리소스가 정리됩니다.") -> doc.save_to_path("결과물.hwpx") -> ``` - -Once you have the `open`/`new` → `edit`/`extract` → `save_to_path` flow, you can expand into the rest as needed. - ## What it does -### 🔍 Read · Extract -- Text/HTML/Markdown export — `export_text()` · `export_html()` · `export_markdown()` -- **Rich Markdown** — `export_rich_markdown()` preserves inline formatting (`**bold**` · `*italic*` · `~~strikethrough~~`), nested tables (colspan/rowspan safe), shape text, images, footnotes/endnotes, hyperlinks, and heading auto-detection (`#`/`##`) -- **Document ingest gateway** — `hwpx.ingest.DocumentIngestor` detects HWPX and normalizes it into rich Markdown plus section/table metadata -- `TextExtractor` / `ObjectFinder` — iterate sections/paragraphs, find objects by tag/attribute/XPath (`hp:tab` is preserved as `\t`, roundtrip-safe) - -```python -doc = HwpxDocument.open("보고서.hwpx") -md = doc.export_rich_markdown( - image_dir="out/images", # extract BinData images to disk - image_ref_prefix="images/", # prefix for  paths in the markdown - detect_headings=True, # auto #/## based on Ⅰ./1. patterns -) -``` - -### ✏️ Edit -- Add/remove/format paragraphs, run-level bold/italic/underline/color -- Add/remove sections (`add_section(after=)` · `remove_section()`, manifest managed automatically) -- Create tables, cell text, merge/split, nested tables, image embedding, headers/footers, memos (anchor-based), footnotes/endnotes, bookmarks/hyperlinks, multi-column editing -- **Edit formatting of existing documents** — alignment · line spacing · indentation · paragraph spacing, paper · margins · orientation, page numbers, bullets/numbering -- **Style-based replacement** — filter runs by color · underline · `charPrIDRef` and replace selectively (`replace_text_in_runs` · `find_runs_by_style`) - -```python -# Find and replace only red text -doc.replace_text_in_runs("임시", "확정", text_color="#FF0000") -``` - -### 🖊️ Form filling (byte-preserving) -- Query and format-preserving fill of click-here (누름틀) fields, label-based cell lookup (`find_cell_by_label`) · path-based fill (`fill_by_path`) -- **Byte-preserving structural editing** — cell filling / row·column·table delete·insert / column-width autofit / shrink-to-fit fonts performed without reassembling the document, preserving the form's formatting exactly. Untouched regions are left byte-for-byte intact by `hwpx.patch`, which splices section XML bytes - -```python -doc = HwpxDocument.open("신청서.hwpx") -result = doc.fill_by_path({ - "성명 > right": "홍길동", - "소속 > right": "플랫폼팀", -}) -doc.save_to_path("신청서-작성완료.hwpx") -print(result["applied_count"], result["failed_count"]) -``` - -### 🏗️ Generation · Official-document tools -- `hwpx.builder` — assembly-style generation of Section/Heading/Table/Image/Header + a hard-gated save report -- Official-document tools — `official_lint` (item-marker hierarchy · "끝." marker · attachments · date lint), approval-block presets -- `advanced_generators` — photo boards (image_grid) · meeting nameplates · table-based org charts -- `mail_merge` — bulk generation of N copies from template + data, table sum/average computation -- `doc_diff` — paragraph LCS diff · old/new comparison tables · reference-consistency lint -- `style_profile` — extract and apply reference-document profiles, template registry - -### ✅ Validation · Safety · Low-level -- XSD schema + package structure validation — CLI `hwpx-validate` · `hwpx-validate-package`, `hwpx-analyze-template` -- `validate_editor_open_safety` — gate for save/pack/repair/builder output, returns `openSafety` evidence -- `hwpx.tools.fuzz` (seeded deterministic scenarios · triple oracle) · `hwpx.tools.layout_preview` (page-box approximation HTML/PNG, self-verifying) · `opc.security` (XML entity · ZIP compression-bomb guards) -- Directly manipulate OWPML schema ↔ Python objects via `hwpx.oxml` dataclasses, with automatic HWPML 2016→2011 namespace normalization - -```bash -hwpx-validate-package 보고서.hwpx -hwpx-analyze-template 보고서.hwpx -``` - -> For the full list of features, classes, and methods, see the [usage guide](docs/usage.md) and the [API reference](https://airmang.github.io/python-hwpx/api_reference.html). - -## Safe Write Contract - -The representative save paths (`save_to_path` · `save_to_stream` · `to_bytes`) -**decide the requested preservation grade before writing and return a receipt that -measures what actually changed.** - -```python -from hwpx.mutation_report import PreservationDowngradeError - -# Save with a receipt — picks the strongest achievable grade (mode="auto" default) -report = doc.save_to_path("result.hwpx", return_report=True) -print(report.actual_mode) # "patch" | "rebuild" -print(report.preservation.untouched_part_payloads.to_dict()) # {"verified": 17, "changed": 0} +- **Read & extract** — text/HTML/rich Markdown export (formatting, nested tables, footnotes preserved), XPath object search +- **Edit** — paragraphs, tables, images, headers/footers, memos, footnotes; line spacing, margins, page numbers +- **Form filling** — label/path-based cell filling, byte-preserving structural edits (rows, columns, autofit, shrink-to-fit) +- **Create** — composable builder, official-document lint and approval blocks, photo sheets, nameplates, org charts, mail merge, old-vs-new comparison tables +- **Tracked changes & TOC** — redline authoring, native table of contents and cross-references +- **Verify & safety** — XSD/package validation CLIs, open-safety gate, a receipt on every write (`MutationReport`) -# Force patch grade — writes nothing and raises if it can't be met (fail-closed) -try: - doc.save_to_path("result.hwpx", mode="patch", fallback="error") -except PreservationDowngradeError as exc: - print(exc.offending_parts, exc.suggestion) -``` - -- `mode="patch" | "rebuild" | "auto"` (default `auto`) · `fallback="error" | "rebuild"` (default `error`). -- With `mode="patch"` + `fallback="error"`, if an untouched part cannot stay - byte-identical the save writes **nothing** and raises `PreservationDowngradeError` - (no silent rebuild). -- `MutationReport` reports `requestedMode`/`actualMode`/`fallbackUsed`, changed parts - with coordinate-tagged byte ranges, three preservation layers (part payload · ZIP - record · whole package), and three verification values (`passed`/`failed`/`not_performed`) - — all **measured**, never asserted. - -> Full parameters and the `MutationReport` schema are in the [Safe Write Contract doc](docs/safe-write-contract.md). - -## Support matrix +More: [usage guide](docs/usage.md) · [API reference](https://airmang.github.io/python-hwpx/) · [examples](docs/examples.md) -Actual per-capability grade (frozen corpus v2 · 2026-07-19 · real Hancom 12.0.0.3288 -oracle). Status vocabulary: **Parse / Preserve / Edit / Create / Render-verified / -Unsupported-but-preserved / Unsupported-and-rejected**. +## Why you can trust it -| Capability | Status | Evidence | -|---|---|---| -| Paragraph·table authoring/editing | Parse·Preserve·Edit·Create·Render-verified | open 476/476 · authoring gate 58/58 · render 416 | -| Table structure change (row·col·table, autofit) | Preserve·Edit | `hwpx.table_patch` · byte preservation 497/497 | -| Form filling (byte-splice) | Preserve·Edit | `hwpx.patch`·`table_patch`·`body_patch` · byte preservation 497/497 (wild fidelity after the structural-defect fix: silent breakage 16.7%, typed refusals 35/66, produced pass 17/28; remaining work) | -| Picture insert/replace | Edit·Create | `add_picture`·`replace_picture` (verify complex objects in Hancom) | -| Chart | Unsupported-but-preserved | no create API · existing chart parts patch-preserved | -| Equation | Parse·Unsupported-but-preserved | no authoring API · existing equations parsed·patch-preserved | -| Tracked changes (redline) | Edit·Create | `add_tracked_*` · real Hancom `IsTrackChange=1` (Hancom refuses PDF export → `render_unavailable`, honestly bucketed) | -| Memo (comment) | Edit·Create·Render-verified | `add_memo*` · verified on real Windows Hancom | -| Footnote/endnote | Edit·Create | `add_footnote`·`add_endnote` (no independent render gate) | -| Native TOC / cross-reference | Create·Render-verified | `add_native_toc`·`toc_verify` · structure 15/15 · page alignment 5/5 | -| Encrypted HWPX | Unsupported-and-rejected | no decryption · encrypted parts rejected at parse | -| HWP 5.x binary | Unsupported-and-rejected | not a ZIP → `BadZipFile` on open (convert to HWPX first) | -| Click-here field (누름틀) creation | Parse·Edit | existing fields queried·format-preservingly filled · **no dedicated new-field creation tool** | +- [Safe Write Contract](docs/safe-write-contract.md) — the preservation grade is decided before writing, and a measured receipt of actual changes is returned (fail-closed) +- [Support matrix](docs/support-matrix.md) — real support grades per capability; what doesn't work is graded too +- [Measured corpus metrics](https://airmang.github.io/python-hwpx/corpus-metrics.html) — exhaustive real-Hancom measurements with caveats; low numbers are published as-is -> Grade rationale and detailed evidence pointers are in the [support matrix doc](docs/support-matrix.md). +Development status is Alpha — the API may change. -## Comparison with competing libraries +## Comparison | | python-hwpx | pyhwpx | pyhwp | |---|---|---|---| | **Target format** | `.hwpx` (OWPML/OPC) | `.hwpx` | `.hwp` (v5 binary) | | **Hancom install** | Not required | Required (Windows COM) | Not required | | **Cross-platform** | ✅ Linux / macOS / Windows / CI | ❌ Windows only | ✅ | -| **Edit/generate API** | ✅ | ✅ (COM) | ❌ mostly read-only | -| **Schema validation** | ✅ | ❌ | ❌ | -| **AI agent integration (MCP)** | ✅ `hwpx-mcp-server` | ❌ | ❌ | +| **Edit/create API** | ✅ | ✅ (COM) | ❌ mostly read | +| **AI agent integration (MCP)** | ✅ | ❌ | ❌ | > HWP (v5 binary) files are not supported. Convert to HWPX in Hancom Office first. ## Known limitations -- `add_shape()` / `add_control()` do not generate every child element Hancom requires. When adding complex objects, verify by opening in Hancom. -- Image binary embedding is supported, but full automatic generation of the `0$p zP^}N6%0a2h%F3}LaY;!@SRBghk5v^FX#^~u`}yteP5Ci2qQnJtCs !3qd#B4&bFQW` zUs**(MN#q2_qX>3ssdY%J jR#*<@WkB zZe6a;AxYzJZ;G+P{Qc`*UJfK-YHDi2s!Kb)*SldvwP>|HL(|5_CZ!HD7;@_LrG}R~ zw`{pU;eCmcg5tua6zy4klI01 eW&jEoG^*{E-gjaJLELsFS6Im6=fu~+%OP*GD44-aFv ztK^A )6 HOS`ZB z6j*7%C#VYL5la?vN}B!a+Z2?P^4_P?<{I2fKEOFNG-S&ZM5^>&|JH8poi9}aUL_<9 zj*O_<$!yGzwRClLc~Nw+)8O`O!~3lS5t$D2nNao)Bc5+84eU!D3TC -!?kFkg{al>`Y3_fZPTNJ(^G~925p(z!X^*3vDC0spPka=zwvep2M0%2 zTpp6 |n92JZN;(s6@E+-8=C`+3SgulasuB ze0jOK$$^p!v=xps_cK#3k~qx{?zWR^ZfW^F)h9oA?IfyEq=3qo{(UMW##HWU3L#W! zsaqumNy*ycth;V7R!|r;#E1+rj3U6yD`}Lwo})7)VUm%Naao*nT3_MIU%Q~D)`^%b zWIK9cyzqEG($PkqWoVjemi}}lUJynlh~F`uk=sN$pX#^n002~s@gki z_rL{Mm%uJ}<%)4%c^UQ((x~aT?xDIc4yhvKRzgVud5S}uUdi(VfmNl*zz-jix}|w~ zR*tp28Vl8+1S~wLbMfNEw$_A ZF5+fzL}c(YBJTQE&QC#Frqkn84+cR&C<8G zw(|1xCk_9&1n_nFavk;TNe+&eRVHT-Kl OVqz7`b0bP89_=Kj;nlAW()C!+2WIUvJ1rnkC~@0PT>RZT&t1T}z2AjTb8z^7 zvHbMuQ|`&9?z;OUxTI2jb?xbzXhcax?%uB(dKMb0@XC;yON&jI;%=+UC86LWJ2@#y zhvXZAE80bp#8lckJcQg C-PP%v+M=5~;+~*a>Nw zneG7F*z2XGrDFJE#48kxxpqd_y6NfZfXyvgx)mGitEzl4*($p-3lcr~czIJ(NtCZ& zKkd3?CA6m{v{1TIxi+{=%Qrsd;Bna(mns1SV?8#OfoW{lI*Y7XdRe+($;Xzt{C<)Z za%19%RHCA>tnBC2-&3f2@PI5XkuQ+9k>Jko@|qzjMMQKxI>M7Y^8FX_`i5{N?zwXT zaQ>n{wKA2zMxG%#U$Rd7rvO@Ca@uy>qEGK85ZTeazRr@DE?!iYo<-aXO&nKw5~-m( zGB?sNI3e4sS!kh9AA^LTsp&$jYZPB-s;U+uDP-xCF?;FXzHRxnC@1n=r(RBY=KG6o zDJuCx$RJ}(ANKCutCzEc@aPq!oTdWwN=|=XZISHeudl@cIbXRjiTBxpOp;DCiJ5kq zE&7ntmfH* fM`lYAe cCKA*;}*Pe=FrAj-%dw?Ql$ zoM;?P)k&I^_eddVv>gauTwJuxiAA~9G!#braMH0GsdIed+ F3eWVVvsBrSJ7O zH`a;uF)-r66W6Zye#gScg `la_u#>ek` iDU0{ASnyj wy~V(ROSFV0r#1xmeZrn?75% zOz&gy_Q?JERjX@nA{I!CK+OgrTGXv%vP}QBBhYN=-T vn2|z+_>?= z3`GEadeDkSzKMac@k!w{nTr>Xv9Ueq*0I0ni$X`m(V02;LHWzn)bUB0p!GzZF|xiw zOWkNO8I(zxBTWhZ`zA(5s~J*IxHoEA^p=EH%kBy__xk0U7TDO>*iBzFo)= b%n(@5=%}I=zgntRJeYlOyM7u)}hD91!|bdlhX6c3Psb zeV`REn^hk_D(B^>aW2lzMxHew u-SJltRsD zr@i$J4Y9xKew|@qd0AlIQ~RYNqf)pZsFP~keINb#qJ#vm629~2wHAX?Qd6;kcut&n zwPBfxVGpUG&id6?v58%<2!aLs5+S5mV*f2ZzWlp)w=COl-MUr35YgeB+XHY)oWcTe zPIJSBmVF?7$nB)C3(HSzZ}P~_%{`kM=Hp8D ~qBlSFhH+efzelNkLTZ6q1WXzt*v1i8Uc7sy=)m zbQXy>OCShaS!GMJrv9YkIeS*WlJyurzkO%+&D<-iD4AO2B;tZ}!k@~hfKlK+b;|s0 zX9xf(w>qc1y81G*`mFys<&|rIL|r+zE=Td3>lgOVIF{P~M9G?~ne^(_K02Y-?`I+f zt$(1v0lPImS6<@0U_yTA(a+ADmy-_BlYZM)j-8J9oOm^i`p}^|i=fz8vD;scGV_EK zBqx&)8Osg7)4abK%s@BTjwqsR< Rupx-oSZh<-o2UGGl;^w z_8xUyUs*u>_E?!e3A&)(aAIsM-?+u_!-M^y&w4UG#Yy^JdVI8t(UhlIOjG3id7%E6 zFJBtGQlh4$yl#4mLV}l N-0czNM&986&Z$J*q*q9BO)TG z*ng|ApE%*GGxAcL5N_#;+XXJF4jX%dvM@s?YzXApS7J@5UCPYDf(>3 0oaUnvBE`D_ z;A#2IUWn0by?0|}ehlC`-cTJC;&q;$2-ze*zZyIq*o#85uGztwgA&r MaF<9e4-i+~h>C{HE2LEff7Fi3^|9%1`}#It#k#KyBd!y#+>WEd_Kz}Ya`E*Q zW%`Mevc;r-Ierd5bL-QiIIhjb9?N-BHgGawx*m4mZiqA)7lzLsi?Qvz(Za6B?`~9I z$^r&!87LX@U^6u`N(VzToFyPEyox& ?-o)~9Q8)Vw<+?;_rywn_@s&iX?%|imlg~Z# z3ffC21W AdN4x7g} cPu|?Kx3*#Q1Tb_>~Sw0m8 zq<-VweGRG$VEB8>-Eez7Hh1o@uluC@^z#$jTno9@L3Ld9pv3v}=SPn{O(l(Q6%Sqy z8DDk ^vt z2m^dtWIE;H^(fQI%E~6DKR7Q=j!jH(c$Afv3a{6h)jmB*UL?I9$a(hclalLW6pdfM zeg)e`A(5PTsHWpgKx_BuTx@DdUH92RKWWwjB-?>_(s>^=N2j5(-7uuEv$J!XAKh=~ z1Vl|$+u*vklq2Clol<|a$#S^N $xhfz83w(0bzG#dU(gC*QFF20jri$gZ3grPue zN>Iqj%fGtqt|Ii}3?E-8y|k>b$HsY)uO*7DR^r>XdCGXQEl=DR$=zS5*v8p6E5Odh z6~kvrZAT=?FfXdtuZN4e3FUujjNrMQ?Br)>`n8UDO)#t^k&=i=z>dA9>&V;Po(9MU3>1Y+%IafQ(w)$H$xntUWvqG*~yBXIVt zUS3x#Y0&cWGL=Q53nJkWpFKxr>z!<+47c1~=<;hVXGuPuXzgy5+sl%hc(x)6beHqq zJN&0lBU7AAk-I&Bsh!yTeCU*h6!%vUc7K^XokF>gsZ%B+x9}XH2=E^}#l7>T7>ZHS zgW(4C5j?&t=kEwUC*2XDudbodK4;umR!h#o%DP>5mJ;PFFjB;Z4FoTn;m_7!g-`Q} zaC1-L(ZDP2B_Vks8ilm?=;6baz(?eG)a?pO*FfU~$I6#es|Uy4CENqKtEP6CX%Iu7 zkb5uMi=spmJQ4>RTW437LIn-SQywb+k0=RS4VG_~VR@u2PXq)6n0GR+OiiPNX6DgT zci*P1-P_IeBiD8LPHNKV@0)shiuHH66LOvE7WTzs;k>;3P0{mCO~+U|pl`y%(_rne zp?`yoKs}vrUIEIz?`V$e@aH4W?v}lssC2u#RqQ4Z3{x0u4f7_x=97tS?V1^Bqm%~O zKnJ?$!G4yVdk#;3f45CS8uBD^0ScG^H4Eq@*-W%-TWk z*c*TO@zFSm8%Poerhc+zwIzI_`qdQd4`M*LqXwUU!bJQF{~;B=+}~Ba4!^#prcPaw zl4=ag(kfPLQ)EUZh>V)`b0AZ*@NudxGbL$FhtA`ZN=XzF`e8wS0Rf4b^K)}A#~YD) z>4fbmBvyV;$*a~L^MXpkt)82p(r~jaRhe&JJh)JyJA={-JwOs8s8PheyhDIUl)pbe z-j?>>=^3`dj$OMpXQ~*yg2KbY0rK0J{0mGwEIa!=Ep^( zHIA~Ho}N_ LU z;urGe%F0Tve{^zka#2xHYw--|n30i@L)6svqmAz{v5k$VnV9Z GBtF1+Veem_b@mW>H z4kKgZnw<*GOFX9xIs%!LImeeI-Rz;PT@I+V=*aBFgAATv{?(n&fxnyli&cbhUld@G6wol|Ing-jv7dsEE^FfI=X12oIOD8$;r~}p+4T;3T0V?rs~=t zoOWt;zh=kEmr9b%9d@v_vYP(=yDBQdN8dkK(I8+$y I?iuVM-_GhtJEWii8>U05pV?GlQC zWS*^m{q+~>8O-AZB=7wEd>@~kzLh7rxUPYK-MxDTTE1%6F)!IDe)7`B#}M}Aar zIx2CQV~<(XSpyl=3VRtuCo$y=47yrcpGW2p7>ED|tEnq8&Ccizm@`BU?D_eb87$YD zy1IiT+p#jRfz{qP=A@*IIdq;t u+mbOdf_`9$~|q| z6E6Ta02=bg*;eDS)i_ff<@t{39NF^;VgsZ#4Yz(3bs~B4?ai|9K8B64zEcGS1^oQ{ z{r&xYn{oJ&PT{d=_Hf+sy6+7&HMOAbtP_NrR1rc!+!qp4QkIb#fk&^8)JZWnA&`t6 zvrqNQYPE9v8!kv=%VEm2%%|EwG$ESuDh_~5191 ffvQ(Z!d!Jv8{>swQ+Q#Y>a&zl=DPPsl9ojX)*=5)9-5vJa zC)wD}yRR*Q0D%_CdGch6vh~>bZ9DfCU@FjeL~vi9-6Nx{Rw}Kny>6*aMv# l-W#@KIvM;&0oIk-Bj4R<*nF`0p0A#9b2}%tx&VKUj%J~ z)N$b8L8Mva<7L-!C?1K4iP6#8$Xll00`~pE3K%t9n-^s#iAFD67bx%%#B004-~aym z;@{u=`{JKJ`S%C^zWC=i|2Mt)zqqIW^|t?uH{Y$eVcwbjmrK7cqoCjtSblJw7<=;0 zNw72!1*T_*uI}`_X<_k2-+hYb5P}w1vbDJeF~rq6!#p`TIc%1`T;G@R@zmTplRtlI z6A1b@Z^pfNG3`J%fe5&Rj2c|qs~0ccwzfX48t7lgRALS}_oNUWmQ8bKW35Q~?lm?t z3Xh1O6LszU_N`nzQUq) w%9SKVrS-c1_Ay{_HLN4H6AL2znZtmBKf?DA4guh)6<0Lgnu38t$H`0gs9}w{~`_ zUB2vFqYqXe^y$;kP``=;eo_)~*(i+Oym?bp !ruB ivG@&Ek9TYNMkG39Oo$n#fHnD;PBt#%^wI`1l*Ay(HpLBKb)n zsJXklBVTKm*s7P>pYs0*p0O6(5f%~cS`l$t|ENq?!iKSoh=UA5TOtmK)!Dh^@#Aac zQ|0 -wt+KCA4Etks&w?3>EDMWYV1z*H?AZDjlh;Q*>te0rArYaW%8H6V ze*93ZOqyR xA3>GS75#L9T_UziVOIur;j*bp4vYnm^ zT3T8f8Zry@@Nq!6AK%iXip&e|9P)ot0IaV4-`{!e+38tt=r6@ALBe-Jg+26HSlB@c zs5|YM+Wx=K|2-7%`%W@3-2{6GMFBeu%sJ94coWSWJ@4S){l1mK(yaLT->ZV3YH{#P z#Ak$vz7jhQ5s~Ps0bpUb*xSD*Cy(ymKQrB5i7Fdhj3-wfDE)LG3uqg~B_-{t;x;yO zcuYdxJCqS0^sEuz4Rl=$4Tq5lVOwfgdpQ2^sZ8S5dl%&70vbkOHRO*HxieU8LetUQ zOamnpg$@>nq}^M^3~Rxom~_KDsGosVO1NJ|MMYIrkF_Ohaq)Yeo}Okb(840XNP&t2 zfe9rp;pNN9kvX`#0O_@LbS(7sZP!=q-i-X&=i-#reVzMPfJzyJ?Kwq6;3iu`>;iCQ z_DX;=1Jp1eG#(TetX_biPEy|$3wdQ0MOOu z4#X!%M@Kexc9hR$!_g&tK;s-xRt#z>S7>&6T7xM9*^7lvw3i?tLP5~c(E*>Nn9|zU zSAieqo?< GD^NnK71=dbW;EXe8&L9wArhA~Kh1DfmK)}YN;~_ZC^6;QK zxzqpN8~ZIs?fJ83D#l9Z&Yc6T2pOfZwe>l4g1)7AFNuCox2S=^d#9 2%c%ZEMd}U4C&1qyyli~!Cub+88gBDhH`$o;%NQx_92h}E z?O9P*2nh-%z{xp046zr|2^g3>p8z;!;P3k!BLc$A%g4u1Uq3k|CEuj&p~ij)J-Mhh zu3WhSX9)^Zz;kEN1=v9_pZ>tUprmB2_>HPzFa7z$`}d!L=MVSRIvxu6`S7D2ybiEB znef}#+4TazVa~uXB37q$bE4`-+Cz-R5~Dxwcv_T&_}t)5$c<4nqh7FVivNgZfCty8 zOyoSW=aTX^DPU82N=khblNH1_&`D;f^S*s^$2!}#bI;NjZgWg9o^|KWop38;rD`1T zt*oi3fs+I2%8XwVx;us+Q-QmK@(WW!F0IlI_lN%eWTd3CC`4br1Xk !9Xa{lg||a@Jk}RFAWc$8VCf?v(Zo;sC56o|>v2&wNg5HQw}JfNOjdoFm>+`J4NBDVCs z3l{ lMFdPW|(Kk+>r-#&UgF-FDas {w_X7|&g>igj R)#M;J2Nkyd`)*9?C zO!Maan3BmYAVnfQDu0-`tcaGWP6m%Pko$JWLsG!g1LEE|o!oZvB6F}BCsyH}osyi? zN+S0-G1z(v+V_D<@KP!s3+xcL4zJ}mSL6>wUu=sC>w!z&2HIa@#l#$^Z&+GRK|Rh; z&-2N=*LoO<3(;->Zrj(!O0c@a1(;fp@a!-Er$BI8>1sVYQ9~o5xZK=3hzBE|BW~Ti zDb7wiH#s#G8x+(C(Br i(|!bAZ*T7moibQsg;`hz zP|#y@;< %%FgQLGu>}PMj7%`5j>lj0 zPo7CWy7w|0C2oO;k3&Oe9PydSC@0Cb-v{r-sZ|t2;E$UJ%Z4~cvTq-h%|uTl %e34i4(e z%gbZbk++sh3yqq7z>fejPF_Yv$bRy$UE-4`ieS}Hgp3tOw_f{hCD``pF1)}ADpoZO z4csV>ZKsZ%I6+H81Akq$juWO8tO6=%+3?f-82;IiYpA4%?n2Z oh0-u078Sl 4XRcI+=*IQq0D^C3bF$p8$$sr9T0k#>GHIr9D+S4d4`EUOw>YaS!d+! -Hg3IY&C@t_jf z$hMC`^m9!OsO$&O$o%|LtkSS!kTj75*g|E}O!`qH0a{tWB?M1sbab>dt21mS*a`rQ zknf4fkBls(s{l?xP*{`5b-g;JXy{Pm%*_7bpOj?C$K(Vcg#nO89+GC=v$GNz5}OTh z04)t~tNuJIk;tnWm@z4z!5R}V? xr%s6aff~3-j~(E_yW`am9G7^l}D8ZS831p9qTX5U0E~^MI2)WUx)Z3L;Yxif_;d zRvG4{jF)0n5e~g3u%9}4GH$C)wC*bA*Li+4E-_KL@+ZnnH%Q7{VLi}405#7Xe#CS8 z%l7Tt)75hBf7FwU7NVMRBCb>-)>6-?B_n4L{fbnA8v-#{Ut9a|@#AOv;(^cLAJ#1} zQy&ny4g7{r3Z~(F(Cyd&%Jw)+At)a478s^6q_NoGD1{;;Y1WHiefiZ>$Q@Xf_WFkc z`H{oKi$Ve@qp!?G6?}rZxprGTA6Pzc(P#|9u=VBK+GRJ?nRAPH>+p17&0LE(o2cOM zC*{L5<*I4^iW~VF$bYdqm_;##z#Lp{c^Z$5SO~ ;K6D0dDvo> z-wHi8H~5a&aIDJ0Mee)j2#^S_^rWOu{Z<&m 6`I9fh!_X3yKV7l`im2lwyaM+xs++0@c<_1d*-a&iWF zUHB$M)B}Df7`=bJ9lTQvw1mVXCs*Iv`ntI)K5QHzD$H#e<(EX<^($A#cAew>C+29Q zx5`4<8(&3Wudk^It~klVGlx71@)k)a AxLH; zN%1WhWGtEEed8Slrw_xZVP$KZZUs|h7TkX(#>Q91RY@M!=(oSpx1f7;^qeC6faq>Q z#M3Qzy?HhQ!wS=dC;}VY7}Ev+lw!ifSc(Nw+27h W AEw!6iMAZP*Bs}~uf0VhYDGF9U zfj>q5n&g?}nP-2-8r`eA{@>_cWkVaH5t~HZX=dj8C7cu_5I)DdZlKoxpN)rGgpd4r zC~?XScXSKkLREuZLEbY9K=&jhq@uVBL^cLUj^v7rj7d}c4m+ar8WfFz0Jd`%n6os? zT68diuAh&EXER>oxEG4NRm_0k*FT?45 J3TA*ST`|E? zQFfD^!H@mhfN^H$=hNM3ktmKT{dp%!ZR;|RO?Ol(*g~60hoD6esh{yXH*YdMKKtiy z?z62?oIQJ%=#hc$2!Qy> HyMh!@Sx@Aq&2^M??{_l1OpE 3X(8| z2Zr~CDNHe^mdB%PWd1}WVuu|~)V+J};ITu-BYHp4;+I0`?j&|89z1$fRvWC=Yv6h& zDgND{9+q9MupTh!%)WnrtN%x4FAxR>V9fel1&@c+49q0gA#4i%`19?=L2PMi!k#9# zb0T&O+D{ryUVekeMl?vS!`4W;Z{+SST6PcQ)LI$p=6Fb1B S2iFYyym1B(T5X_vVAGHRs2^}?)Vl3d^*fPe*eK*hJ}YPRFY4S`Jr|0ewK{Vegm z+3p^wbQ8Kiih)awZ8)L4=PUZvB$W5BfRYf*{m-3lp#p4DI0P?O^1Bi$biLFgUbLnV zrU0aq>zJ6BFh8HZp>O3+R^0T04{_J_?GzI4(a6IA(+_AgaT2G^4H3HlX;#3pLw3dD z)O>C2?eu6tioRpb_~(0o1uB@?*ub7#rpipH8)=3OVMrj2GW5ntWVVIb*_B~!{OI4m z{%gD%EeM*L0P3C;PvDu*{_`>5bM9^y1H&QGWc)(M*7xt {#+Z(FbIbzy*`?n+ wDh7N zX@yV=7-^zYsdDvdTJyw6eH27DVy_=idD?Bg+qZrHUe?o7Z{N=TU1e^$aGmeapAY?? zhYb8P82>hU|NG*9<2U~sfAarVulc{X^6!iP;+y|{&%ZDJi#Ko3s-XmVtFsga_Y*vP z{UD_zB_+|*3@*fdZRB})cqe*WQAfDAtooRsvB3&%Dd3{>^bMVz;qPv=7VJR1sO!Ew zgxmpOib@unjeO%ha!N eJtC z?`N?*X+@lE;WLGv20`cPNhK6(n=E4e5E39?At-^a`2PI{+WRBFP9R7D#SpuK5E)BK zgu?2S353?BrZ@p&iwSTe7*7Ddyu3M#rs?Vjgo xJK^lOQosq nbXrM&>Z4m;^E7W>(h-^boH_ zUFq+WW)4PMG%D=_5@6UpI!Dx1RNxUfi=KBB8|>SROijUBya&}4^_vcDqo@)*-{IrX zzJy0?YiWTO_1PpprVV=EF|XG~O%jVM)6>)7-LS+#;0?iC0~Q42_K6edX8VDu2Bduq z(@_Bmaa}mxP^WpmBYG)rO?pA&Lg?f;c@nCvmzNhI&*09TTm<3#d^=PFiL?-0J~TE4 zebfVyUAQ67$;W30DG1rUps>&qdJi0$m?oORV`wFTUWg`H;B2&X`*eq3N!~geLD;;F zjxGQ maC;!OH8J=lm^7;EvR`0@Sw^g2Tclv?BrH2i`DY)cpd zj>+yagTVv02tm>Vb|u^x;dd+4hw^1M{1-|ZQDuaOsk>o ?r&_rnn8yyCHlOA^v$6#_M`3M~jZi}gJu=LP>r0AIps32SM7yE~5;5`(6 zShp)$Z^79uqMihM0H<_@ZbdoBP^?=zF?Ux4szghs6oNBIR}2B-68i1NBO4p9<4)}C z77zq=;64G22a${d4aG2wsPP5o+*V#g(s}&&3*6Tbo4c`GB&4zB;pNVM2g|tQ`50(D zK)hdjdvAXZ7e8j-wYIpjVERXBjmASk=k$MAd!F7Bi+4dbj{MssU^$u{LCB2e)B{1U z4{PQ?S%Of4R*Lr>g2>rKR1XpZ`f#Ga#Y>k;N=xhPQ~1n?9##P!9+VPCd5s!TVw?a^ zi1tem6R;AN!S6*B^4n5F!9alkh3*5Y1OtQ}77!q(Opec)TXqEv41`#tQ-L_Z6=sTQ zj3axn;_ZxT1R)Du*(abV9X}3>;r5+7F%HnLZ1Cxz#sO#GN+8 c^-EVW5O1J^ zVW+|&BE}e13>)ktJ&2%a4nxX2eB{X3_&6r3B=~-^G;92CpW@ ;2I9ap`|PE|l3xIb z_}fOIe>Ot)xNl|uonn|PFt8Qh;Uqz*Ct^vA0>HnrCoudec6JjCF1QQ*tUiEu+$4$# zaJlFYJLaO~?!EyxmlSg_9#0MIIagAy?{&6>n7bDkQ7-^mgET@qMjlfZSs}^@d+B5b zWj4U3q7f3E42n=#Bqb3m@u0Zv4x6c*u1TmRz{&6^tS&D@ck&x2T}`U4t_GIC2@#Nv z%)ze01`ek)G9j_}#-2r(Le%VJtSZEBFv=clGgUxKM33|0BvL0M1xb;Xg_G0rlVexm zvqWu;kOOrdk^yVqdl)02OCXdykBQ-;Ac3VOc`O}_7#2Tdc$^1xgeOT<{wx>Q7nDwL z-qhCU g<11*k_Va^d0v Knub*Ob6op-0UohRsC0?U!nx2pgLg0M;c FcJ&!X)eXShHirRYP3=Fiwg>Qu->ma` z+>=GfQZN-$P*GiMb1lK?2g+@pQ20>df^w&X2nE@7r5b_|(a4GmR#BrrDn=a4q?n0@ z=jTuuf`4G6AVGGBkN*M10elG7x9(gL?ia_=z#oA!7M3!Ii3ypRU;3?JR zrC*`C=#Xs7Yu0(1S&3T)yPKLCCn{M(iq36_pn$_0^e)II_F!qu2s)qjx2<7PkYLf3 zg*uUh39A7>;FWyiVHz4mhG*5)(h?7%qN1*|Jrz0hA}I-4xQd2GYC-~G#H~lVzP$@B z0el|K!Gm+)ifCf;iG0p|y^D=VtntZ;nXi#W&qT<=BmxA-aPx68ft`<^zrC@Mgp~A& ziU^Y&v~e^T0SGLvAb%3emWYU^yx~-u#Pilaq);HGMqN}_?}Fj0t<4SwK*C)Vt_fTe z#nz!v^AIPHS^)Db`pQN4Tw9T9-oCw?O3p$_3NOxBTnzcy2N2yww766mDV_^jOK1$7 zi%0N}%YEqTNyt||c<>;YOkBVrP^H<^`Kui}P%>jH`aF&XAVfC@7fuf=xs=G4=_qw7 zx;g$LS!!SThNO+R#7#sjaq+(vMleh0twZHMdO)e~XyjEJYilMi&;!Kw-}H1&e*R}N zdbtY^2YrVjIRw_|r>3P qottG9Jx7UPMF^@`3Q!^xzygu=|cM>-6qd!)a zJ@wuU65bc3KIqNB!HvzAa3#b&XyG@O(MP=;DEtd8d5TDD@K_PswNQ>B!$PsZ=>oBE z24R_r#-c~K#6%?=W;OgFT*wGE5oZk^=H$#kQ$g{Ga!3$i7Y8Ll6hgafR76C5wD4~z zB~-tD4GiSzeI(ttFA&3nXabrZC-o>lP#_RsHRq11&pgdu6axf@+j&=fa{560-~@y7&^4m;dGW10!FD3!Q{b%2T)(p^dN)ez#MEP zcofND*Dl{akIk}3J3XWyB;4pHPcXETzkcbg*Yf7mjBxhkl|Xm-{8@Idug&75I;=*( zKC!X7l}Vt}kQ330Dm5ezvlFBrv=g7i1i&eo8hRBzWR_rQg0gZ-%l7$x$CY_w?*%)! z%3NK~GvS &a798oH>*8`ZYmUw_-)y4Y3X-dM|peVZn#kM#5xUqtzuII1A~Y z;m8qVef@bDZcXIgqtOwTc2f|ks+n3{aAvj9YRNr;JK#G;A4IhLbHOHLWKvo@%_dCT zSPgJ0ngx{+>kalG=%DaBMFuWl%l`x_0Y6oN<2}$(uj_#s!(n3p9T2^tE|-YIcZ4!V z=zJxTA<8f~Kz+QtfNepf H 1hYqm6Der!##4Y` z#32d^Rf#}jf3P|hr^n{HgG4oW$<(pEq0+3aZ$G9!%xVpiV%|+cf^>TZ9 R*iP<#$H0B}C~ zYT+mVkd>@I?+UNXbvCTwL`AklL{C>yF=R|&SHvSiQT$oS#N7$ayx`vRkjJsB5%&2( z58y(ByXZmg-ArIm@blq2#X!T*{jrY01wULH?36L@${oV`hEoSOpaHhl(^C>&OO#hc zCLM>Q;W&@3Yv(lhm}KCF!r5PrzkkvBe>}^__p>9b56A|3(($MV;j%;(4)<1O(#9?! zZDKNxx}69%l`CMi6h|sRy_)svRn^EGC>asB2SA?G-p+Ow!Z99tdhrNP;H{A2V8LXi zAi=RLCY>j{anFD&&w=8A4#62UR97Pt)q?lM2lB0Er>8%nV0O`Yp@=Pvudi!paJX}a zhR>uGE-ex!0Db%WY{3!Sr*IMqEw;!l1YuNo;BR&_0ho9NA?oAv;nn8EQD!Gkj^U&c zqOyjH3#((ifR;9;%Nenk#S4%T(}zar1AU8F3*fxBMVzxSgn0oNH6^wy}Smk0+u9rhv(?|o$y6i&C>Gn7+hzpNHB@06MNv1!TM{u=wpP>L o9Y_sv z*LJc~;qv8I#~q+w)AATTLR&RBQzIiIq;?!P(gS}QT(aSjktkJL03JSm9Kt#UV2C<% zYp30}1aa(rhVza=8l$iS2iqC_ZEPOE-$0GQviG7uO$@nt1D-vvAV416sZvK>jNps$ zA1Vw|zzFEFMoE>PkpWjmN_+BN`Q -AEY6S#KTC z4iT~-X5V@3K8OHjg-ewSn)&o9RA{7-CFJBH!RcwmF?(AHL)Q#QF6Cs;QZH?ze6$MfYI}HF6dx=-xmPlH?!nR>Sz!$ZRU!^X``gmI|<#x@g^$?!7a2Q!mCCI)5zpoQ*Y zo7=qyZV^!imVg%pN^jBFKuedq9%;-s@7+uKYjN9!Yd7r`oR~l?@qpC 6e=x{PbqE3t2^GG%dKEciIHbkiX26D-q_ecy0*X> zlGqT*ypXQX0P1uNF(5kP^vlt~LAZ~~Qe(EEKLZ6i*fijGoJ5KXg&&pykZCeKD>IKR z(ssk_+}9=7a&W&00az#iB2WOJFOPj`!@lQK&-K#LdiqfhXR^>7InrjYyN;6?5RY*3 z-OCmd93kc~r2{Gmy |$xXGLp6~Q>5UGJs!G8Y_#@;%t%6)4ab=fVV zprV4Pgrt%J3Q|fpNTX8H($WSfh=8=TbO{IuNGmBNN=gVK-O|lG_gwpX-?P8-*YR4{ zwcRQc=6s%K{KmLz0RIQi8(0TPRGT!E%|L}2f)zmh>+9p-F2o&*mKOI2I%oQ`e!we< z=3mToIN=)h{@`WxS-@Xnrp8ZCbUotEg^JVB5dcQ8uO*Gn%LXV7C{CZ2YluY0g(?Z| zB(0fQK)?-^>t7dxG(;f8r|-p)_?j8wQBm%j0QnG|@*)dxPGZ&sXiof#Scp?>Z!S^e z+6Bi{N$!#F>K21glqf+)>@Pl1VzHm^lo3Mf*2Q?{Fz_%3%wJHOLm6|PA%s&QcS%G# z9c_T^>e#=dA3et_oPiMX4ULQt4UOvz@1 z_k(P}WD_$pV3`Dc Ss2d{KJ$t-qsGv-yA)>H$I%F6bBV51`$I=DRpot1QrWRE2m2* zKoH>Fb# (Iq1icAT;N(~%%uzf*D zpW-aKt*#Vi9ztS;Nd{dBtYm285$GgmCuwY}Na<&&uTSIV0{k3cJSXEjl|=t=vo{y& zM;G+_N71T)D0hnhNr6uUi9JNuJO2s(vtIvd616|v6I4E1f=Uk&1;&pLAKttQf^i*% zvF+Rsuwg=C_;7&~5Gk&VcdEfaeNhmKpre4So1R|GF7YL*J(K#!F?24deC3<-hw>g= zUYo2UN^&v1OpK4qe@R7~7XDUUMTHia8>tsEh@+L!@&Tuq66>Cb!iC4}4a+P(rg?$* zjipI$%%^A%JDg*W3wg%I4rG;YJEF_ShYn#^A29{HEp;&i7l(K@!U3wFK$>^VBon0= z6i9kyHkb^h4k+`9*N=dxN#ifkC>^j2B3TdD!x7I~Tbm-eH%$*`)4w1zRG#N4k@7cy z@fB>wyEI1FNC2$#BUz@qJG^$#=R6-5RJ>{52 |3P>%Z>+$EUl($h66Z{iGQLmv;q23I>SE%-FFk469w zV$0K!n>Pyi2eBn%W@19~g%?X#t6bnia!Sbb^Yi@9*Oi%GeYy9kuu!?E=!$nWGS)MN z8{fhTgrfxseEFDK*At$nr<-{2+jVKr9HctI63U0djRFG)U_avNn@5SQgj@*jJUS@_ z+q9JJcATx>M^%uB{%2$)Br0kPd?pOY- !=lnzxr%7gEJI_t=2h2j&31JPH6*mG!Q0HsV$8Up?KPMJplDi>44= zMF^m8s>U?d$319(@g$);K(wI=`ea1#VSK4X9nWm9=LQkf)z#IO@ZFdg{QI@NBtv|o z%>Wk Gup9AM2At%2rdzsd1ffU%J3E!3l@`G?hp1%I*H%@)=JY`S4 z6}Ln_VnQYjjXn-HJm1JV! Bfy-9Qo+chD^x%0p1FELJ?s*k&qu_dE!JM@$&SGVfe<$ zhIR!tyP8|zKhu8{SXSgOnL;F_TujYLdE?5)C>|vYgFsO?cN?D+rJy^N^rkXn{_(BuL?VR;88B%aGY*z{Zg^PlvldgB>Oo)mf$E? z`ecPrO`P~{8&1Jrh%7h=a17M%)68(V81u0Z&ttR=nh*$}aY^r2K*O_4qSVfs5gTiX zyJ;c _6Hsd57XXi#xt#gkz!D$cJBVZu=*Ykjz%mkGHiVZ1X{i)71Q5#zGCzFYxMuP7 zb(HXk(9nn?e-&JT?Z#_v?OYK9p?{S7QY-$tOGJQ0hDs_3&Tf1-Zl49I{ypH<`*SdB z0x-p(SD-TKsq}Wy7S
Ep-3eJo5mvA_?cXk?HJ&hCHHpNDW*-LF<8jl^E)anEE@I@eruj48W*D?iZUPdqE z743Mc-DWll^DVs4OCha+m {Xt!(l2So{SPnzvrALn*5yLjo(LvSt6RCnM zp_9|oz$TE9q`z;HRH*6!R1W5?Nk^2qrqB)I{+(N11_o^bbuz}Bu#k`re0w}w_yrzN z-@t%H{bNc+JlH^_As;(^^$AJNy)Qd4&4aCiWYPy(6*Tj}wDhi?Mi~dxqyLM4(5Lf# zXlN(S06bEsP{~f6!rF~juZR!}yqAX$9|laOWRy!B`K6?65M#4Y0der(L3&oC<1&+_ zbeP|g);93XDPjn$t~x3L!GAwG;mJo?2dz1YPN?CAQ}CKDc>J?x=Tqdz>*)>Q8-aZX z4A22Qo@hGRpYkflXVDQ8WCkxa@>u_sji-m7SAR*@`uKSm 2=M%m7zc3OhJgvbPDwr{9Heeeu*LAHfDGofJ&(cO1cip$V-`ct1N)|U zq5=ej62-F#3Rg-=9s)T6F^DLFO=3&Ws*QdX!}|!F9qw `Lzlt<&hE3slK-qZRYR+bL0nGu?39|r1uApir>e2PUA`~l!&=|E?wNxcu z6iomufVVN?51LT@7GUIK+-UaZc2!?2xBVxFE!TI3PjLU4>+jtr^E1-=PfrM|s!%R- zT^8WGt0-GrWXnz-A-Z4um9OmTIqB1f8%Ct_jx42hzb2(T{h+Jj_R|eQ@)Lo5E=og; z2h~%)7A%xsdK;pqDfh5#h(OqP@_=ab?Al@sTTaPGrcE<3SG(vJ(fPKM2Bv%L5}WUY zY-Z@wBp#m?TCkpP%j1m5H~O|cS*PzMLVAWY#Q$ymAY=DOvoCk$Y2AnPhn){>E2*d_ zSs3V<8+r`z?2nT>wdf~M4?F;u9H0&WH )$ePan78$yg!%;iF%kIW n)`kq6F;NnA!Pt?@fJK`mc?>e|Eco<;E zTXU6*{&<|xaH0>wjGOBwnX0{GRZ!EiINy4Kie=ZvTeY{OLP4t%nv}DZ8-9Cl@9ppX z6kWmpBIf#I$JuNx!k1qx!4ffjuJ?X4b{E;^j`_vxqAGnf)ym03%YWl)Rkj7Um+EHs zq T%%t zm&aSbQ^U-#FJ #Dac|@T#UE_j0VNJY*{v=kNDNW `W~Z&5dD_1DNq{H(}d=Fb)l&Q7`dR+9dt#AQ;)K1DSnHLC~pZg$NDGZQI^S;G6X zP5SG%hV+HR9_U5pxv)P?W1~(GXDqH_y+%=_J|A&h2!XvDqyNY}-)3{U#C^#-i|O~@ zPxI4q|FEkWdzu)*59kU;k5aDZ=)l@P12*-UwbDi`IOP+AaY`7k=crZmZDfmx 8qayr<%XS1hp#{zA8yb`P8So&zj8P294+ut&GN@#rYPtn~K6| zT_fjv$`+m7FTA>NFd=^n0OSYsTSHXWoxTG-1ZKn83r}1$swaF2k( QtOEhH1{>=Ybt0V}y>DdgrR2 zu{XPy)QXLimo5b`{90{F{~R;Q#LdB_BzI51ZJv!~ @tdxAa z5}mtTwoj45)A$P~4>JCGO+rdWk!kWi>wP9ekAdmqpj5L!i5_ZqPtiEPZNZzj7wO!h z6xH8qyo-&e(OBW6Hull{DTXF0U6fxvao`XrKB z#OM>U
#!7p!I{SDF(LrJWqlk=r#z=CR-~&NzCc*-0}BjVN}s|^ZL_AU$(}2$Im{m zE#2Vhv>RMv r5Pch|vPC(1&JFG>Ar<50(M1<~-H5A+^i)(H# zenC!~Kdo_G2m)0F8_k8IW@*L))_;=&;{KeXEIY@NEWP5Z)F!n_s%yFw8mr)E@z{Vi zd5My5cZ{#wiMH6C7X-fYh)uy+@&ssmsB2a+58rX28tF6Bb`uZIbNPCE6_}Vy?jLU{ zJ<)!LVsp9lmfEB3#p;D*)>rNo1s2azn9uWWS#0HpEHjshh&&*iK#w$+TYOBz&Nw+P zR%M~-Per_q^kGffqq96#x73BK6l`u#UuLJ#($WZ=i3?g8-l7|o--X;;)1I<+$>uxm zGa`9wl9n$iFRj~C_00x%>Yh9QjPl=Ps3&_zU8l`mwYOKP757s^)aB);V{khuBwr#CE&hXOzS6*IOnNSqEjNtWAXYHXL{>+fo0>7{y^G~|%z z@Gx1;3aQY?BTMeX74tdsY0 =-5A%CzOyJTrs(M_bc{s#=TB zB&USLtQ20QsRv8uWRsnoUzjK4ZV(0|?(GdUVpLpKQC4K;zSQu!_bt@Nkx5C&05{ml z863IDWGrST;}{a|n `=F4Ff z+*YCW?y9dF_`xZw%)Au2_}=atfw3`D|DB}P!`6)Fgi1r>@QF1ZhuhMPDgHHS;l_TM zP7L$G2d%95j@i^0=3cvcbw3f{-LXx6U+v4@@V!U+OovA4&CW9`bGE OmFhUJ!e>%vlvBKLiD(1A3^FR0tKHY;M `-v51zha z3#?|s=knKKhGUhLk$- G`xIiO9d zhCWO_lqeIn{8RPsoB93|HGHPG (sr zIQvcrl`A_?M?C+W$-~#|ros zMHQ`X^htknadIU*w_Qlv_!U99Q{VN_2Kn E#9-)z(uA^Hd=PGXnCq);1R`=M~r}VUGs{XBH!fG%JrU~oYl4Df{Rq9Xr1sb z!F^;ra>0iP3z)vCY3~I;D^S09%gWp1($eRzJ#e4m1PFt*6&nT-08*9n)}-Ta`=l9? zG&M93Ba;A|rgy(X9yKzm0PAjJ10v)AJop+3Zjey`T{Z{=`Df3^8-P566kLn}PzLV9 zB%weMz`Uj9wJkS=^XjMnUF8?zGqyMke?UK!=iA^ukd+cl jo zYKw!LZnk1vQoUO}y17?c%4AEJo|$HPE%1~>p~I+5 o~uyMEI#IxtFj|C(-(YnGvXBa@U#&)0~%Pi38> z^>u6ooEn?-}(F=-+K*WYV4YLE2MtartlEw|XA?KPAy+nYFDwdta>k zJTAI?qh;mGph>7X @?l*a S!6gXfHY}_j+W_dJk-;P|#ZTl2MJ}c;CLy5 jUH(V7jWs0tP^(fbKD-%R}iv_FN )oRt(X63`z&T>s# zubw)>Y|HWz8FvXH^#hL#nBVdntv}|9*t-5(@NdtG;_ar_-d^P{(=*)9AAX+v97D@0 zB`M9yL=WE6>wtKah8?%X1K0Tqe_oL z~bm zgsLHbmO;$9FL%47tev+vT_0@xVSPh1`c>MzXLe;@?QYZlPd!VkYwn+_zW+Y9bRcfF z-=nwKr9WHIsb_?%n^%2)D?P$4GiGHWlKR`Fixp)hkJk@U8Do7062wY?)lTHxx}b{X zoe&NKAcof-JgMY Qr79U5cVk*{o_)Ywh% =xcvYNpJ=ngFK ztNoo-zUP4)2lUr`` Bt*dog1Ekg zv5)F(3>fB)yQ^(co3{x{pa!=SF0AP HQ z96l0p-PzIH&etn!?q}VhY4x8qwU;$-*wb*VKKE|DXCGrCZI)5q^MfPzs%^1rQK5JQ z^m_hss_h1|JD=H`t;L%(vs|h@5;#lF#lbc }1P24mz&QCxXh`G=LokM-k{ntcX0u`O_9JjDM9S_vImADkzc{ zV<6N7`9x=12 #(_KNs2F**FjD39yT-ZH2 8LCjayzrWA>-j5R&nfudP2?uU&0B zb sT^fA8aGx`4G#`I1O-Qf1PE_PQyRPvdIN3FmGq0+mtVJ{r{uEy^!Vy}MyJ;7 zuHvIBXTCf2XKQgzEnIL{RaeurDqU||3CqcO(lDry{e7^G8MpP9OM7c-X*P>YD+w`f z7M`s~LB}5Se9Ys5{Guu)q5Xl?dRNcv)c=RS5Z9dnAF_?uY3xCml13)eyJz&&LtwnY z!K4!^;pH?%>$7Osjz$Wyoll>LD{=uJU?C0YG~fv#QNfT_sJ+}5aDEST6h!s%i(eKv zKa*LJU6#{wQev__y%UIeDrGn3W?}-D?F)M7!|-b{24MTU=P6j~1$`mmibXO!BOr1h z<5qA3dk0gy1bjfWv?IW=kfhj5{6)030oDvH=d-;->DT#q;od(xehZj%l_<-)#27tH zojg0n$S{Y}{y|e!Nfm`4;O(1}e|fL6-( dX{2a~Fj92OoCT4t4 8v(4pa{ a zg~J#{RONy~0#r0L=bIjmo*L>YSopm9(|yC9^8M c{+Sto)4Dr8`&`Da0LR`a`Mcg|rLe5L>PU zRSqC>^nP24yv+tEu8l!If&J8o#aVQuv^@d$%K7usgz=dfSMX3Tv{pp9DD9`amm+#r zut1BF;T##6GPS451&w{H=%hpp%IKr!VE1wRT+j-4@wg%2EGux;MfGzy?{E81kWo>| z?&o>*`xf*=h)7eM6Nq+L*iVf{J|{5(m7St$%7cAVXRo{4<|g&(iO^bowlh-X>F*Oh zCM2|Vcf4vc7(X*MJ~5fo^*%fBPUnXv@6pW5)pQ4f*1T03P5!FUEx2xvTmK9_C96X- zL~HyoB|SAghwP-PDY
vhton7i+)?uK8pSLb9N8Xcw*c$D+uog4!Vtfpwt!Vaef zd5T q=7I-;Jv7BJZ!k`ZAt`S1!c1{ul5h|sNgR`KcWD!`hh zBa0!KT1rOMS8kDPjY98nLHV`uer&;D#rp1B16hz7w&buH@~5ROFJ5|+aUBVSU>u=a6zZS&{reHL`@BzMc&u;pdtwUYh)&AyXnsyr>OiKd zuF(BHaXORPDMr8{EFef7(!hHu0`_%N(Jn=uX LxgCWNt*0iuFNknA8vgX zupL*GvHMp1 1yv1S2HLX%WQJP z3g)i-hi|J15UC%a4k(v#fX*R6>@r38#0-Du;DbLsJo)m9cQ3jBJd^jWxgo3U%dTSI z%XGAdc5mvJGiBDDP|aHn#kq0#^&;8zgTuo@5YUiIR$a6N!z+JZ-cla=>e!@^k*{-= zVwGgp{I0*1)PTk`#1@r}|Ab>6vc(fgKB6R|BwL|lNY#Rdj9NrF@6TgWM E{P>Z*J`IV1%D4+7Jr=kl>)wMA&Z^%-LrM$!VgweLCv5n+VXTpkU7Sl~ zh3ENe!S6qRkVX)nnCNkg78jHBrSCNVOSkdy@2pp-#rtnHe-_hGWjVa)#;reexHu%A zn@Tgo=8Ty_*U6^)F11y)G!Ko9pxin(!}F)8bC{y9K&6edEox)=O=(T-q h*oSDu`l2%FX1<6b}Nn7uY M;gyd9${i!>+6iGxR@>WJ}%9) z{iBz1D{TT-asz8r@kLTEn NnW2JQcyXl5zx@5s~<9_|eN?f;-(4{r0|1>r>NlQ&%FMqr+Iqy?3KhJZ> zki(+=aLfZV%3c`JmDO!wvRcCYH)L`pYVX>E;EY;iKxz$G;*8gG+Bw;=&k2$-Wc;=q zvxlPtSMA@#H0Y~FCnrt7TVL2PLl;EckitUL(?iy_cdxSR5u&D9`x&^GM9eqJE}}4A zUPrW&^Z8_njByykv5S5OqAj3K*oFddXtL_=I-Aia1>u~~J *PP~*obY%UWsjHs_@*+U8Ohg _gk*cr1uZ zdQMTnkSi?gq3swq0@|OLI!YIVFa^LsfQrH>r;2cS4uGY9d-Z8qj$@lp6kPxD-y*S$ z4nh>W**+Ki|3%@CSQ`?Zq9zN-2l *8bq!Q{P`nfPX_y{KLd}P zpp=vpG<4(iW%nyzpd-psoh#3ZK&u6(G#r;KI?CONnnYGMSUXb!T5(9t5mmmF{Onl; zsN3<_hUa^aAk&z6jkm1|>ty-w+}a<;Wk$jOaL11Czvm^aPbU-^r*x@xjgN<+lhs%@ zO#mBGRi-s5f8qte!ruk6COM100KrGE-(bJW_G#^RT592V3#T_-Q{ ^UTWP-TbAN&S# z&W4^OtG!*jl`~b4Y35{W($h0LXT7$!iMfe#-;GZTV 3D9h+1HtNPA2cVq`hpl zM)>0$J1Y=3@5(K9ngTs1Y0pgm7|A^_dEokbVl{hw_m z7CyVp=@suZV?gJ=+S2d#U}bY$lDt{1Y`C+3W#;-PyK%>?;=a|dCl)=o)62dr%tM0w zCWcTh+liqStb5A^W^SVvJ)l%J6kSM&1i6lYN+3!|nAbDZLP9VRm;2BK#1fpHX-_^g z;-Oe_;ls#)3gUzi o5$p2_xW8V2*~Emp+3zrk#P&8-2v_J5(ZgOCLx6)3x)v_RH& z{meec*b4wgUnpE;Vj&~HTh6o(p<*Hu2`!QD%av|7HZ;+>J6o^SLiO#xRkK#U|0*YU z;kZlt&nnL%*Nrmft}q^Z3x~%}tD9x_N5hXWo*mq??kG^^R4J@1ZA ^fAHx z%e~j ^B4`1YJ~dV}BDK~_<6!7Z6i yC7l(kJV~$@8&!u2Q<*U) z1jx6D%M^ZTd&5$IZgZqL#%;Qru=gVp^$OFrH7NZGa1g?d-*A^}9; MMNs zI*(A@6k!O~F18t*kv=9QwseUL?On~`$kV3+zb87;6`VEP{j6L{lh<`yuJ3nAZJ#U} zh9aA$KN9 uf07yau+q(bkA~S=L#8_!=fA?Uwj9?r?X&n zsvaKROY6SkuVr&AIw$^^-4*ggWz)AW__2#_{qaqTpDu5K&l-@oGq;r4%+v#w^p)|A zmi?7#eGXr#HSHwo;-y`7uc?cXE~TkOp*5sQLv+8tC(RMtssf8%+kq*4nz9G-@^fX= z` ePUk(y=15>-6kc ADOJQpVQ89>GP^2CJVZgqKoL?^>dq_`H{ehzpP=lwGRtNM#zNfD}Bf(hxCOBAB zD=Rkk8hCr~8#pUBkP3^ xPCR0y&rMy4P~NL|-R zRdN7L)AVP*Pfc|IOa&t?O7_~u1|2OeJbCntj1V)gOm$hqw8hKIyZY{*9k-q{o(O(L z^MHJzM?geYi f8`B%~B7NfZ#A@aSa zQ_F>fhpI6@*hgfPJC%+dIC!&YR>V>Lwzo=C_*UMx46oF=NYPvdrptpXV}%9D&EMsp zqaLLhh~G$63J>#7P0Cd1*(o+J#Vn<)6lA9H(|kiWe&@@|VXN!do!&4o_esg(LtrVp zcQvoW+ 9%F#P5^ZJV$BNS+ShoY`gCH(YV#B=>=JNMt5Y;+Z*;16&tSh zX$};rb?|yw2Ht5E@WLM2(3z;XG7-0<8$uRI7AbCPzGtcR5*j`Q4YjRP%?%$?(J6i$ z*f2bG{X<4-)Y3c3%5|P2Gr!6^2iQ0M-oHb#b&3c;f{v@}tk1+*I6U{*XYLmCws1a2 zV~urU0o&=rU96vAoJH&tgjc_;aDSjp8eLe!RVI2_aI$(eeu6}Mv?a>~0TNJ?B3K3u zunu$`*ujO)yAq<{;!mI6I3D|6YCU?Cx~i+o0aXt|SrF;~9|2F{_ZXg_M1&_FvB_uJ z|IskIg!E`6j4Njd(4IUfdhrLMC{D3%6I#=83ntv1dhaIq3vow9nOXn6l{WQwh3$9Z zVp(R5dhI~CeG#06-8KIHvB qn4xhMl@Z26D zv3G>xI(EAHMW5OeLf6mj>C)2cEV CJGQZoJ=dLqFNeA z5(hIJB_(oPwbRx0pxq^+q@e#eM;DCI+u0K#Yb~F;uCB1nyjQF)>BQJ4K_#M&yBVbo z4I4ik_<&K_*c9x}D1)_xZVL+pT@iAC^>+_b@^Z(szaX6WG`oS6oE! vXlW 9dcRyGo-Oa516+r6_k~EMjYu` zxtp`q?)t0t8s5s9dLMb;r6wUMfwqP&I5d>~j8uMwjw@Ycx#jKU=GTj_fuRM31W}xx zupj&L&`g)+>>aO0iNRN|mUuH_{G+cWbfy4w >rrx~WB=Zog}`|Sm0bGW~i zuJpcL;FBfeQf$y;i~B6)SJvm`H_Ol=?bz=nU}bF;NUAt?*zwBf_d<=AA7piA`#u%4 zyVe4>;iPC9k2e&4E&xTzKf)Wz8-bA_;rt1sp3SlM?R@h`EuuTbC;N6e?%n5d{o}@1 zVc@CcRjDe4IC>ERA5;Wz*#^CFfW;jEw}&oMx|ZhzTNehqY$ ;B`iFZ|`3huqF$^-e~oq3be7a1Lbd<-ERNx*^gKpR `vV+k#~`ryQ40pN;M*ANU3>!`n?&Gbd%H`p !J%S}B!Fbxk{fiq#R?~_(bbo)HT{${?)8OG{@SK;~3WYoE92tWC@@Hr@mj-=m z8a#D$7+Ck{uttw6*{^q*!n#L8Tb=GT$Jk`~P4Sf(|KO$dF4S9kwk^ETGXAmB$&aef zda-SQy>w&cYQfb@T!#qr%$z*7D?g%K4pmo82*&?l`E~Q|$njCuh=nSG+QYxeP3Z;r zn!`IF_BOH!H|!w~Gz^ZUE>ws$+jc#c>>EuJShs#`I@hJ&;O16|@nFsyd#Vb47v`_0 zVo5})*Qo?ERG{Ke4Yjhv s-SKYo@w@9DRs8ilpRs!)1@;9Viqt zRFLQiedaiaG358KD^u}1KZdkW;^jO>L4XEhEfh#h3A!B**Ud5*v>lHY$9|y55Tgl& z%wQCBanJbGPkZhp-!tWrPAHDuiLn7bRtMXy~2yM-CjoMu#PMztULJj_($HLlB i$BAJYsliLaFsvp7hHOW_3 wlx;o$Swj{Z{Ym MTHpAsH}J~g z@Lm@b3hN}6$EP3PJ+){38dr+?(fWH=XG%ZinN0S18?ty;lafBsm<~T-`1&lrf!LL> zu++AL8}Fz4kv#A|(e`XxUzvsEVA2t9DxQA6n2>ODBYpXAsrydGeHDnp6@vowX7V!p z^R%)oKq3&!9c7tAjDNwxzCW=CHYodcK7;gNLMa6)99VqfxHO@Up?l@PHYOZ=^4oAW zqR8!lJv|lm`A6E-VLjk{5RO#!V?e*Ca0vS}=OHR`my1MtHFPML-|PEFAi1_>XhW|7 z{tbu0cW-XIe*l2!jv4)}RWnpD8xieC;G9=4U)cZbf}H<3QHqQg>MFi*{UJ?f1Z)>W z^7ypNA1hY=$}{Yru|3<7os-Yx!iti6h38G}=O+U8+0Fi?wR!3pDvDB>ZT!2OY{$Bz zYX?>S6m>AEm78tNn(^iH*cfDGi|>g`q^H@K34a^@zANWJhehj+$%5vFI%f5BgCF&4 zQ6+qBkF1#sOP(pj$|cYym${v-82MntLE*Lg^2K;&4=GyzvBP_VZ*QDlFYJ&diMvvl zO6t}2`AL7GQ*A}VzN53{_O)NyhLkc?G^&{*F0-Yo-sf@pa#)LYAb>JhsI2Sc#pAa< z#(w6N-dU(Q62U=Vy85Q gG zMAQa54ftI*->&x5*E1lBMKw#8)Jq9G6YSq;({te %1iZMJw zDj0In2P&W7ngqoRZSLMzY1I@$L2<_XXLgd1PwT^v47VSv$aw6$6`youw!CEeM~S7V zeL|4F_a)jW; d)I7*#=oT8ksiR_~x!Yr`{0+oj z;{g@O0ul}7EV#P!>AM6bK4m@3)<7MZT_PZt8b29M2}je9;cE;GA8g%X;MWVir*aHY zQ*ea}R(tni!OEZ6ip=VA!gjID^YNj?nOSH6s%vT{ILiI}j%~hGQdud0NFXdb3*RlV zbsXCmEp$@EN<;80RK$=ySRA=<8>@#%NplcFyTjAV3ok6qllO2SJQIacKdFQvQj?pT z`;?COZb6cS%^`8?_eZ8&wKu?fJ*0oaX8KSsLnR_mu~Al4#UNyMZeh0n#UqDa!At8- z`Kh ?U zL^^c-v+R$O8aSZ2g@vrx3?dvcUSz0d$0e*)1DeiITj(6(7-1A5*)7Q6E+&8PGwF88 zau`!mhRd09myY}p@dM)Nryv`UDgPX=#2_sxPUmVCvnr60nNr )g0XE)Pw?vHNHo z6e^?{Yrn8Nm1?Z`dcB$+X-7ii_py&9y(Lwvmk&uU2ppYFahz4YcmN^42YhZ%cl2nC zE{G<#GBY)~b!ptYZ}cK&=gByN3#JF;J$RN&OEH9HcsC4_vs56@?97zvj;mL%sv s}6Pl)RkM9`n z+wtzl!`2b07cn!1^|5mFQBFZxC#Q4z3o}gYMefu*eC3|s&C^ZFX7zPUkY>8frb$RP zi+pxxe`t{MTOsP=qC&gbL9OY|hbD&BqvP$C!pWDbXh2z- !@SW5J^g)GRm8!W(nDx&X4*&RdAW8^9OdX& zfe?s;Lc$6<8XKny`d!mAFV*X@;jxVhG!~syzy`9(ZT5>7;}Rw=$Z1m@!}dRY(UtY( z>4>CHk-Nzh2GReBhR$MohomI&^wXtBR#v=j+s-)r80;ykl!Mg#-#@xPgXL6FHyi?1 zH!$KuDqBrp&?&hN3Z%lkD3R!s2dPG2H<=(FeuP_>4`!1;5gt9MMHWK#z(`MRNx1Q} zK(WSbrdAQb Csm*3zoxQMc zDXsW!(4O27j)zJXf4@F0e|MsyvMfWVZ2Wte3(cDDsAtZap005IwRfOupFuwif(;?P z=}Kt_r?Kx>oIRKDX+seE>4-A57y)cYQmHpMLc#GCCM+OejQ$}R3{v47<7f`5`XPNx ze7pzCK(N$t34emP7&)tRl9(gm&{hq2CyIyi%>(gz?}FbNf#O3kbRx%x`cYnX3%(-_ z&cR|M0o=-v+F_Y743UWng_@bj67iwnq`*CR|NfjsA^FAYD)!ViyipCqfW(Rl3t`BA z{~?|089uaIYiz>@-wJV+uIiASjNAWA=p=>!;(uu;@@{KgXdwF_DOL9zf4XhqL4RhJ zm-sao_Rq6_4II8|^j2|);J;r*{KK!Tf=NL;cLdf{WZoj#?Lq&4{w1TFQP5r~>g8R} zF3^jh{Up8{9YJ3X$+RGauP3Jt?iQ5uyhwb_XBYk+vO4)x#ia3C^?$#@&T`s2$GvN) z{l$Kg?SDr6#Q%K}S6+ogk))t|z8C-N70P8i!{ wa$yC)~;`n1pkGN8@W(6I0T zF1Pi#C&{cd54=3`_B{WRB610egmP(}A^S;Jb>pJKL>|9d=4Nf Ih!w %Z%~$qses>FZ_mel*=|yQXApJ3=EPF5~mcbHs{=%dX>g z&27i}G>2&}CN6Iw>IjW+ouWZTj*}`g^znONyDigSpyS)Oqmj93E4u%K{rg8Eb5t5D zGrb@A_!u=VoWzJa^LxBhV9 K{k~!#X#m;5;WUp)itF6JvMul0 zo(bfj@Oi1xP!_d!$+R;|^n6xc0~fir$$cY!!yi&Ceu2^9b2N($FIT7-UEZBIbxgq3 zDa(lpkv35f39sp@{xG8yc-&oLH+g)k##}phYo^IbmWtnP?Z#Dxi|PemUOOm0FDrfD z`N168o7!VB>5r$~On>pY^qQ7y94m7^= EWn>Zc4NW~I22P&?Lru9DIccUBf8^HC-X2W$xu1*sD4}&+vXkY8 z)4a2*_EV*c%q1H*67#bNze;^0(j&ZY-P^I_V4}js5IsW!-19Qx68_@)t)nt$*o;Yb zEKc>94K&hF_}osF3$l{9wBhIUXf)P8`+h;6R`JqI{lJH M|oY(DmiA1(Mbu-fkdXsWbon^kv3hmsb zedx#@Ky1eSh0NI>49jN(EuQULI=#I)*U(H=Y20p^?r0J5HXQcHwfQxZI`Jzp0 5QyCw5~kEUuoBoqa1F|K9X{{`)SLP{yx%q`|i*+27G+ z*EjYCNueoH&$z9)y?0^ZkfZ%M5~_&LdTbE|#`WC42kCbH&xxkc^fgFf$GsNS_C7Z6 zCSTvz4r@$u87crUxn4>WNjx?m8AXPZ7%VtV+UZSG-e05^qymW+2CqiBeChcO?#|4+ zSl?diOnKdUGcv_ ti9bU{W^JaF@& zcn7qx<~_yY76YSx_c!d%Uz~AjF8h`C*k^5R&&h)@MC`alH+vLAKS_gb{9yE|z*+W( zJ9ijuw{(%2aq)Ax-YdATQ|_GmG5?)H3U_Aj@`{tGuHm#oWmv=D)v(4HmlBPFg{s-T z|8w*vNThH`>`MA;_;E_Rcx1!#^Deh@%jFvn|9<_4vMEAUU4x{)welA`H(eI)o%Y9{ znoRNy6AZigy4Uvf-eW6I?lb$ K4IXkC-=VNO?!7HinXXDS~n1LOk6{h=DB9g zoBSVFQusQz&S{;CF4>T*8W|blO8@oZif(}T(4`IE+@DMoKJ_CXI0d>-ADh0JZ# m%SM)U!{Cw^1Cv^Vbygxt5aw8o<+*=*YEdB4Xd1Ok7H zkJ+2A)+MmPbZptz@vVDhy-q^UNQnB?zirwxh%b0rn1a=-^*5PDh4((cX5IHOY$54s znURdyg{xyjzse~5M8%lc53xrb^bk04$ntUI>%~U8V==*ZJjwNYe-o7yNd~kUE2fQg z--0C)q{t;R6v$3`w&|}qFHd!D4c+fw{J3bH%yN|S+Kz)^``BC9_Xr7w&YUMuAKP{O zMEcsbhl-a;O*kloFAdldB=KrK5nPw+{~xBl10L(XZC^t}ldKe#j4LxTvLz%WgzU0M zDmyFcMhMB?WMzefY?a6+G9!}A2q826$JPBj&-?H5exB!j?z?ndzu)(Jj^jMe<47#g z>1k_g>bWi8*8EkFdr%Zx #mp`|Y$k zn2uLpUObcC*f;3rX0rZB&s}g9zk&bDE!t?>cp=9pO`~^)iv1c4JIwjGg8E-x{ -wtn@12_=Kv-Pu|6E6ETTv|>fwDMoSlJs=aUOV5F{M}O*Ket-O zeC@QcV{^Ke=gT{-xh8$#i-5KSzr8qjFjKv!@{XslD8qHe;Lg{#rCCCOF6os%PDy(^ zY#ouKS7 2fQ+kqz3O}4?+ioixB>OzCVzFuF1AF%Qc-nCpgocy^k{T?&~mt z9?l{ArRoE8fou_SRo<5vL_Dm=pGzPaHVA%2{Z-f3WigVKbJXH!T4L|R&~p0tnXB}_ z@2_$|`%{6;J6SbTu~|{r;fM9~#=FG!1)ku1?7VT8>85ngtyExZ{uikm$w%`}7b&$& zwTObB8R12vsYgfrCLd@W8LxENB$JFi9NAl2P*>O&`sfLR?#y*M`lFvJZzm4KRcJd5 zuqd904UPYC@5d$rHqZ~(wZR+LGu2btDIFeC`_&{Yeg06#5)~7@|CosJU`QYCjdDc) z>w9@zgSBFE|LsQ(r&^`T`zfB`F+R4H`WuO6O2irVtJJZzV=v*|N{khqmVw$S-QPz? z&CJG|6L>x9syMqGY7+jG-8gSIIb6vK0KUY1)%1Yibu#Z?qehxix{R?cQ|B@w<306M z^xloVRCr7qcW$(peLU}6baQR?_>D^Ik&i!9@)CCEekrpZ40)sa)k>$hKy>`|&D9DE zyXPxkDrjiuWVs@HtncAnWP67Gxe>bEd9{r}JSgZ&hI-GJ?2A9-?`;p19i4XuC@=#| zzrASWk>c}qi(G!aXHGr3?kYXH`Eneg4TDuKJ%t+V2O}@0sPfYVs%CO9YqgIq^h7l4 zC&Yx9n%=QAXHxs6Zqsgqs8M0Nxwfj4euh$>U)(hGlG0Tr#mSFpU((9W{FvWxCia(I zSg*gUf(W-X-5*~Pjsrt*u^>6?HvRNDrzr5GFkbr86n+H-uk2pg@^+lNA?~`gHd k$PnuZTmG|)Hc3=xlg%ucgP$!d4r#=$Znm}M^5u%xg)sV z_?35f=|{QqZp|O3#3nME>R#ve+f4c1#X4V2j~vO(f<9B~=O`kh&)$Bq+0FEI-%4xl zlUn;HYQmU>UT@pc!rA;gap229!Q=yjKcD%gR(`90jJj~-MAh#C!n=&~&Yg=s?pHZ- zS`Gy6U)|+K7ns# D{k$}@@mF~Z{OaW4r>n- zr8`_CwoQ^HJYK}%ltiAS+DQs3-)eCmWTPY3j3^|v >_!b)z~{x zQY+qsM=2H~DDFQ0GA*2xBu>P^rmrHrje+!qPL;)KS#6id&^D45(O7EtIj=|6_#<}> z$jc@lfg0OmZ71fj79y4Vtlfg-X+ng`r>xvk?> 9?3?`G{=U)w-@g4<8~w%C=3UeC{p$zxVrpw^f{sgA{WMHW zypNs&^@b4osD1mLj{Ms6)JVDAn|i^&yTzU>W-jGgu4js#$~lX<@~3|9)0tVA_|4U3 zn~O%!KawCAD(Q-L6n}dAy(TPtUk}51?39T?RQl}Enjv;Ut|qy*HHx)S2o6w^)k?mF zBZx5|Y|7jux2;gAH04Y+vgrLx6z%`O7 {5A>&$}hEN`&?)m7F*qCVAJu7^| z@z}U2qASwAZ@*=eLyhmQk==#&j3Tr9FttUve3}}Y3~BN}oM>{e&vK&Ml{FHQUE6n1 z_=O#gL}jQ;5iPxWWXHp8;j;G+J4MzSPPue^DXUUs8?jz$YGW95<>2K1(%m}tA*D$; zavDosHL`WLbxTJ-dmrm!m&Q<*qy(XAptfL7wyC_WrK}5TQ{3hF-bXE_oZ3QfE{v>2 zw&|O^ijpS0$c%27WUq^8xe*=`#zyV0WfdW5E}8yG=(qr6(2EKT?0T&P{F_EX&KReu zW@==s%NIc|yXiX_uqI{LzucKf@&7MybVj0Kb??DPTyMTg!q-Ca)Wy}tIP*g7jOS{V zu8z}N)YPZ=C Bc;+Z!q@^e_6?~=%i@!z+WKiykTb0Ee7_dIR^&%dMOOQ2HCu^( zessPBFNC?8-k~#VM8M>C>g`+iL`y~_?5cGWNk7rY(?8gW5+OIl2M2Am-0YL;Cop)E z)v{!-&?-e8RgmJKW~XZGC_Y0f5PN^4I;plG63t+QFY_bWNBdbk-#t4=YtI%g&k|~s zJR(iX$dEX$`rhy3Tast0*;f?blp9~OD6H~yA@}GP`aHiXS}c6$M{! qMkpnBBKyhQ^!K`;NeF}?z1JOX4eFK^k#}Iv7y6f z`qc|a&Xq4ZUAg1>$|uRz7x}@mc@%rghik^SVK1HM9a96@=4;l@addZv65VDK;72}d zKl|HrUpn{liO=ckI#Hg_7znE5()0MIJbPV4uuN#Mm@Ex)2lQXFg9n(Cv z;VFL-CCx^@!)(L*DvJEn&x;K)-se8FDfud>v8m&Q%BQGWsIhs=KYx10n9P$HeaWeO z FYNkCj>(ZH+vK)Ut>D+-24iC7oB3uuwab!UJ5ZV<{IZE*{`Z%&RZ4^OZ1>XQ z%b`_W^ruqiP=dINxZ^LvB{QGuv7sj|l|#FB@mr~Gm2IW*#%AgD{CPTCh2Uc6-z#Lo z+m}6_C&Or?XxtpZ-+%AKm&|s{0L1_@pHhm>7wRCG>R_6K(V{nf)Yap{%;XB1ue+6N zeD%VQ&GbF5tvXVUGaes}&9X*%y7&)*UyCe;EJ6A3RfDdJ_4#Q42qj%MDIFQu`Dc%F z@&h0~(_U)f&vIY*fc6`=bMY5uzkhLWZ#-cc_nal&eV@P6x2@^HoZW|pR{B2oeHHv) z&8TW1{doJ1h`GBGfyiJq_wxoShiT}KW@}{kJN$B45 k|^cGl;4PLwc TrXdiQ;Yv;@zy`T&DzCgQOXmKd|y! z-}!t`InSGYoTgkXvI#@l*{RweBSuGhac%3;I?Rp9qNP|u<(SC4LxK~+6JmZ&Pjyz= z^=IjFL@WOMTu`8zSux@2-8SBpd86DUcFa4^zx7#UL8jr-Tw1~PZmzzlkdQ`09&kT$ zZ 0D& zvT!_Z8gHMOnTffE<=_dC&gV~W4!*wk$!pT6+;s`~&6&%Y1nK{!bSf4Wk#9ZcN2%ra zN^gJAgGR!2QIw5Q_&j0JZX)tUzzf9IXf@M276R6qZ}6NDn9+IU<$9W$nfTm4C@8>V z`LRSle_Jwd^gwTo`JC@IZ|mNA}Ejteb5t2NT&M*)4mr`Hedzjf_&VX>uhhmRA MW DmWIi9-4(@Lt@V+S;%Av@QM|dOuLDc-Ixf zuc1S?7oz#NaS`7I)FL@RwU_Pi-UHs1yB6b$Ytp4g?HYqTLn}Q)@2g2SV~+pi#QCLZ zgSYFxk|Za?#uvW&v{)8jmX{NE=q=XC%YBw;8Rdu|$emd2QaF>Y%64(cqjQwN2%$Md zgy-Qv1vVc24`F9}Bz;O?RD1KAC$E+w$=tH*#>Tt?QXk3oBuR6^oO!#`hk?wr>#Z4l zfx5B&G8EX39Db!`MQy&dF_ee}yvxiTsPVY;3SNpH;EF0rJ$ZxAXtT36yK4Qm<7E5f z_u)?pV=vA-uLQLHa};ZqcOzdkzqoul-R!v;53Rk(B@q_?OpUBC1m`ry%_X75ecn=M z2#3}GxJ=r+I5pNGcU__55j!Q$gNfpt_Kt@aMq`j&|BoE<+l=a?H~Sb5?c!3CU|V$A zSA(x#l3g99KX1 r}B%kh}M zVAhiO+HzhvP_}98WkA4wox(eV9&(AFmn_3mC9-9rE7lgL+yB(vinLPnRp8@4mtR!8 zjlxfnaT^J-3cWlQVQMd2Y0< 7`nN)Y|00ozRANPs#6 x{8` zZ2oz1frkx-viJSP9lzEf>!4VcP0=d1Pk}FTrg}b)P1%52ZS|;)&l3h?liQh*@hkO% zBVDxb1O8hYta_Zf7-reb!yH=wW~IbacK<|oozAJqBhlBJXgfimpL!`0trK;Cp5LkM z_YPoy@#Xh6M&_Qd9Vp7Aq +g! zKC&raTx`4gx-zHqOQ@19_8rQX>$oeI*f;|~)k4kkg8RGTyx MZw-dCNLRDNm) z?mgBgL=-)q{^I;~Elo%2^l$~UIM~zLqGq+8 Iq?W>| zNFOu@yfEx>C;LM&cm6@^P5YmNDs9`JV=0DW&w-FoRhGagm-R`99XrJH-TF4vu?_<2 zTx+fpvq(z^fig>)F>s9l{&B RTGnDqL{Xcv2a9zw1D$)Wr*3dD_`sT=B)f5cdRpDoxrRp{I&g9P4mqN1*;wWzyF0 z-pot3`Up*sb;&ft%o^t}E%zUvzxWs5SFq%i$UDit;A6(ZDzd!3uy|yk{(qr+?nJAK zal=-f**a5juT?}~j=Dn;O0UVLx|e99euy|vT;3BXd+jNAV{79slDZl2gGeI=J3r54 zvmA_Fn|vP`;ZTV><=wD#mHXbS0Sc+@<|(X!&%&*`3+!HwS_^4KYmN9^X zZI<7xHT@N~9>D=ix zyga)yRONWDDQ0HZu8oCaB_O@~NY)PgyzkVOFUUPq>B7)kp SXMvH-hqJq+e#i^2{Yf!cc zITBi={q@)R19UwSPA)+T2gbAF*ka$CzKYUwb ~_^JeWFm9CWJ45jY1V^|T2iH@D(pE&2Nj|w-` zOo`y~LQiM8tlR!)N9DE2^sv*7W1)cu`aV36qFmjrda|u4*tDPQ_)GS`W3AObrg7Fi zn5)OHFC0|SBhJrfHcpVKG=j=I|6z;0K63Q=cUED#vWUrp o@CS8f|C!L^23 zI$LGNKiI-8A!Q5AB5ZJi6Se&Uvu8bwK4DdL7rQ6a{;*nm+})E!4LZ=+$cP=dCDYSZ zu$xWpN^WWZ{THjD>u@YW=W1dCLqhiteOSsPs;gkP$}qpTy**gte)PA^Xy8orqE2V| z_%Q!~zo5EsjRY^RE%sv|8-{#(@ZiBN9lIB2u?C?DNgt@5sk4QH!ajQEsZ N;Ei z<@sKjw#0xpkL@FD4<0>sY%6rgkYvxHL(B8?&WjVluu&)|$R4n>g=QA`v`|9}k!Do? znYnq-LA&TFmYnhMpds;Cn$gvDU4G-mPEt~$OCmI~u{!}pzkz`b*7zw%8VKASJbw7_ z$;hVLR#t5Dsy^P{t1ByD^NizLxrx8&NYCodD5B#JI $car9D2~Pr#wZM+YeHH5AL@9hFr6&-CefiR9 z{rBzB5AQ%sf*MPGLj$Nv(87VZ8`p2^(cl>D@Y>qj!&?d@JG9$Pqu8TrXlXe>k^uEM z>^6ZElB03!_HB5wHjm7^KN?ZCvf{wNo`(nH*hVl((dY!pr`eVcU`ud9j17WJMC})> zP?LGXjj|MeK9f0Vsj2W?qr4vnJMKQP%<)4}+Zq^ 2`A5y$(s7A#=<1TN030grj zu#|#{9XL}kcmbpBn%{2OG*$k)gQ75H5ESg`?RCOR1W1ZG=BF!NmS85V1&S^FT|vtQ z^=( YeT%0-7=jZ9)omib9m^c8EiC{qAexI8-ukBaQ>Wz;-aAD9 zU{t+VD_0{M*52 avH+gR56rQNeYiYU7-KF8qD!YHGfZkDrTHJa#M=J3fq&7OTX+82o;$ zWPZ0Tg;xq3HsKng=Xb6l!uXZNah93!P3mVdlo!5kbur3+DBsMJOeLHh)eiea=!DT1 zXhzuc?eFWuzJ4V{^dsY0LmT$P8*k5^m&L_b6%<77uh~!TFo$3PC{%NE)|Kj_46~3> z!X@P6;em^>8{AIQu3A7}3vd0Nqhl_dR`6S~ll*OTbO{%dWNvN_4#&M!_eK0`Vci!L z6qGk`3aVaka|PKAuAooD!WQvd8;gwY+)+wWCORo&6^@EsgF~;Zv#u^3Ke6UBd}iR+ z44>aeO6hPLh8y2jfJh169PIx1EIaFKYt!)gB^;6)aYY&$pvlgD^5jh{8RN@%_aN$> z l$6d_qLTE(wkKgZuYaK>W?m?=svsCTVij*udca0;@gj&oeVI z!9g0^S&*8$J5VxD(|i^ZQhc!7#`2eCk+6`^D#ZQMtTsRwMqn8#vALMi{x(QvYY_4G zTkSpsUWgTe=0{z=4${{X1UMW+RQ~yzFCx!>$9cfLhnp4-J?`$N>yV+LK;6fWFClz` zm`s#H!}EouQHk5C?YBb6I3J;*!Fv-0iv-G(badK}<%1q$$y80|IvBN_@V9|w8ik+q zZ0%}~gEs665dUNG9w#Qup*IPhCtMN?42GfV0TUoNxjVbMUX40yfC!36=?;oo+N^(U ztd5)81~z(q3poEA;H2i}&Pz>Y0xuPEmC$DDNL jWDs}j1*uv@WF2WG{QQATRmN-b?yt#Y4u9V|3RdP7IVM%&26|`4HWCR7 zIbj?ad|fWx5@k4m w&iT04UCM8unmK^3G8|B$8LYw1(5`JbBYdDBW=O!vo<$PnSirzmajr$jr7vZW<2M+Bq S(=r*v83O5KNl$UYjb{l^3pGu4P% zhhN{arTxGr?RzY)!U<+%eEc3(Vj&fZ;6Yq1!8Oy@*T-}IiT_Ok|44jxvYr3%o{)q0 zD%K3JC)x8tYXNnN%Plx7;?!T2`(b!b!hK@}KM}5RQ@CR(vInH{A@43({4gcuw1GeS zAC* t(rJXIk{{G{gM7{KO)XFciBcNSnT*aeqq4xYM(7F*2