xref: /plugin/totext/AGENTS.md (revision 37c5feaa1dc3460cf196c7beda3a3c10741397bb)
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