1# AGENTS.md 2 3This file provides guidance to AI agents when working with code in this repository. 4 5**Always update this file automatically when you learn new things about the code base!** 6 7## Project Overview 8 9This is the totext DokuWiki plugin. It extracts plain text from various file 10formats using **PHP only** — no shell-outs and no external binaries. 11 12It exposes two entry points: 13 141. **CLI component** (`cli.php`, class `cli_plugin_totext`) — 15 `php bin/plugin.php totext <file>` prints the extracted text to STDOUT. 162. **Helper component** (`helper.php`, class `helper_plugin_totext`) — gives other 17 plugins the same functionality via `plugin_load('helper', 'totext')`. 18 19### Architecture 20 21- `Extractor/ExtractorInterface.php` — the contract every extractor implements 22 (`extract()` / `supports()`). 23- `Extractor/ExtractorFactory.php` — extension-driven routing (`forFile()`, 24 `extract()`, `supportedExtensions()`). Add a new format with one new extractor 25 class plus one `match` arm here. 26- `Extractor/AbstractZipXmlExtractor.php` — shared base for all ZIP-of-XML formats 27 (OOXML *and* OpenDocument). Provides `readPart()`, `listParts()`, temp-dir 28 handling, and two streaming `XMLReader` text walkers: `extractTextFromXml()` 29 (text in a wrapper element, used by OOXML) and `extractAllTextFromXml()` (text 30 as character data, used by OpenDocument). The unpack temp dir is created in 31 DokuWiki's own temp dir (`$conf['tmpdir']`) via core's `io_mktmpdir()` and 32 removed with core's `io_rmdir($dir, true)` — never the system temp dir. 33 `extract()` removes it promptly in a `finally` block; the class `__destruct()` 34 repeats the cleanup as a safety net so the dir is gone even if the process 35 dies (e.g. a fatal error) before `finally` runs. 36- Concrete extractors: `DocxExtractor`, `XlsxExtractor`, `PptxExtractor`, 37 `OdtExtractor`, `OdsExtractor`, `OdpExtractor`, `PdfExtractor`, `TextExtractor` 38 (txt/csv/md/log/...), `ImageExtractor` (EXIF/IPTC metadata of jpg/tiff — metadata 39 only, not OCR). 40- `Exception/ExtractionException.php` and `Exception/UnsupportedFormatException.php` 41 (the latter extends the former). The helper and factory **throw**; the CLI does 42 not catch, relying on `splitbrain\phpcli\CLI` to print the message and exit non-zero. 43 44### Dependencies 45 46- `smalot/pdfparser` is bundled in the committed `vendor/` directory (pulled via the 47 plugin's own `composer.json`). DokuWiki core auto-requires 48 `lib/plugins/totext/vendor/autoload.php` for enabled plugins. 49- `splitbrain\PHPArchive\Zip` is **not** bundled — core provides it globally. 50- Text encoding defers to core's `dokuwiki\Utf8\Clean` / `dokuwiki\Utf8\Conversion`; 51 JPEG metadata uses core's `JpegMeta`; TIFF metadata uses the `exif` extension. 52 53Legacy binary Office formats (`.doc`/`.xls`/`.ppt`) are intentionally unsupported. 54 55## Automated Testing 56 57Tests run via DokuWiki's PHPUnit-based testing framework. The calls MUST be made from within the plugin's repository root using a relative path to the `bin/plugin.php` script! 58 59```bash 60# Tests must be run from repository root 61../../../bin/plugin.php dev test 62 63# run individual test file 64../../../bin/plugin.php dev test _test/GeneralTest.php 65 66# create a new test file 67../../../bin/plugin.php dev addTest MyClass 68``` 69 70DokuWiki provides useful helper methods for testing: 71 72* `DokuWikiTest::getInaccessibleProperty()` to access private/protected properties 73* `DokuWikiTest::callInaccessibleMethod` to execute private/protected methods 74* read `../../../_test/core/DokuWikiTest.php` for more helper methods 75* use `../../../_test/TestRequest.php` to simulate HTTP requests for integration tests 76* use `../../../_test/phpQuery-onefile.php` if you need to parse HTML in tests 77 78Each test run will provide a fresh DokuWiki instance in a temporary directory via the default setupBeforeClass methods. 79 80### Test fixtures 81 82Two kinds of fixtures are used: 83 84* **Synthetic, hand-built** — `_test/FixtureBuilder.php` builds tiny valid 85 containers on the fly (real ZIPs via `splitbrain\PHPArchive\Zip`, a real PDF 86 byte stream, a GD-encoded JPEG with IPTC, a hand-packed baseline TIFF with 87 EXIF incl. a UTF-16LE `XPTitle`). These are deterministic and exercise 88 specific edge cases (header/footer parts, speaker notes, repeated columns, 89 `MAX_REPEAT`, sheet-name fallbacks, missing-part error paths). `zip()` is 90 exposed for composing deliberately broken containers. 91* **Real-world** — `_test/data/sample.*` are genuine LibreOffice outputs 92 (docx/xlsx/pptx/odt/ods/odp/pdf) sharing one source text; covered by 93 `SampleFilesTest`. They are original content (no licensing entanglement) and 94 `_test` is `export-ignore`d so they never ship in release tarballs. Regenerate 95 with LibreOffice headless: author HTML/CSV/flat-`.fodp` sources, convert to a 96 native ODF first, then export the OOXML/PDF variant 97 (`soffice -env:UserInstallation=file:///tmp/lo --headless --convert-to <fmt>`). 98 99### Format-specific gotchas learned 100 101* **XLSX sheet order/names** must be resolved through `xl/workbook.xml` + 102 `xl/_rels/workbook.xml.rels` (name → r:id → worksheet file). Worksheet file 103 numbering does NOT have to match tab order, so pairing sorted filenames with 104 names positionally mismatches them. `XlsxExtractor` follows the relationships 105 and only falls back to positional/`SheetN` naming when the workbook or its 106 rels are absent. 107* **EXIF "XP" tags** (TIFF): PHP's `exif_read_data` already decodes the Windows 108 XP tags and exposes them in a `WINXP` section under the short names 109 `Title`/`Comment`/`Author`/`Keywords`/`Subject` (UTF-8) — NOT as `XPTitle` 110 etc. `ImageExtractor::EXIF_FIELDS` lists the short names first and keeps the 111 `XP*` raw names as a cross-version fallback. 112 113**Important:** Test classes that need the plugin must set `protected $pluginsEnabled = ['totext'];` to enable it in the test environment. 114 115**Important:** `setUp()` and `tearDown()` methods must be `public` (not `protected`) to match the `DokuWikiTest` base class. 116 117 118## Caching 119 120DokuWiki may cache JavaScript, CSS and rendered output. To reset the cache just touch the config file 121 122```bash 123touch ../../../conf/local.php 124``` 125 126## Linting, Formatting and Conventions 127 128Adhere to PSR-12 coding standards. Always add proper docblocks with descriptions, parameter types, and return types to all classes, methods and functions. 129 130```bash 131# Lint PHP files using PHP_CodeSniffer (must be run from repo root) 132../../../bin/plugin.php dev check 133 134# Auto-Fix formatting issues using PHP_CBF and Rector (must be run from repo root) 135../../../bin/plugin.php dev fix 136``` 137 138## Plugin Architecture 139 140Inspect the base plugin classes in `../../../inc/Extension/` to learn about the plugin system architecture. 141 142```bash 143# add new plugin components (must be run from repo root) 144../../../bin/plugin.php dev addComponent <type> 145# e.g. 146../../../bin/plugin.php dev addComponent action 147# if multiple of the same type are needed, give a name: 148../../../bin/plugin.php dev addComponent action foobar 149# -> creates action/foobar.php 150``` 151 152Additional classes are autoloaded when using the `dokuwiki\plugin\totext` namespace. 153