PHPackages                             endless-creativity/elephant-php - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [PDF &amp; Document Generation](/categories/documents)
4. /
5. endless-creativity/elephant-php

ActiveLibrary[PDF &amp; Document Generation](/categories/documents)

endless-creativity/elephant-php
===============================

Convert .docx to Markdown or semantic HTML — no external dependencies.

v0.4.1(3w ago)1349[1 PRs](https://github.com/endless-creativity/elephant-php/pulls)1BSD-2-ClausePHPPHP ^8.2CI passing

Since Apr 26Pushed 3w agoCompare

[ Source](https://github.com/endless-creativity/elephant-php)[ Packagist](https://packagist.org/packages/endless-creativity/elephant-php)[ Docs](https://github.com/endless-creativity/elephant-php)[ RSS](/packages/endless-creativity-elephant-php/feed)WikiDiscussions main Synced 1w ago

READMEChangelogDependencies (6)Versions (8)Used By (1)

 [![elephant-php](assets/elephant-php-logo-alpha.png)](assets/elephant-php-logo-alpha.png)

elephant-php
============

[](#elephant-php)

[![CI](https://github.com/endless-creativity/elephant-php/actions/workflows/ci.yml/badge.svg)](https://github.com/endless-creativity/elephant-php/actions/workflows/ci.yml)[![Latest Stable Version](https://camo.githubusercontent.com/bb3420a023279f1fff1c9eb860bb9cd62ce9647530df843779d066d158ac806b/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f656e646c6573732d637265617469766974792f656c657068616e742d7068703f6c6162656c3d7061636b6167697374)](https://packagist.org/packages/endless-creativity/elephant-php)[![License](https://camo.githubusercontent.com/84607028183b969e61201126868eb3aa4895070352be30c835fdea14d9ff35ba/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4253442d2d322d2d436c617573652d626c75652e737667)](LICENSE)[![PHP Version](https://camo.githubusercontent.com/712b14d8b660a3b07bf03e44ccc021db04d25c37518136f40b9a8ef39f39f560/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f7068702d762f656e646c6573732d637265617469766974792f656c657068616e742d706870)](composer.json)

**Convert `.docx` files to Markdown or semantic HTML, in pure PHP.**No external binaries (LibreOffice, Pandoc), no heavyweight library (PHPWord), no shell-out — just the standard PHP extensions. elephant-php reads the *structure* of a document — headings, lists, tables, footnotes, images — instead of trying to reproduce Word's visual formatting.

It is a PHP port of [mammoth.js](https://github.com/mwilliamson/mammoth.js) and adopts its core philosophy and style-mapping DSL.

Features
--------

[](#features)

- **Pure PHP**, no `LibreOffice` / `Pandoc` / shell-out — only the standard `dom`, `libxml`, `mbstring`, `xmlreader`, `zip` extensions.
- HTML and Markdown output from the same intermediate model.
- Headings (h1–h6), paragraphs, runs with bold / italic / strike / superscript / subscript, hyperlinks, nested lists (mixed `ul`/`ol`), tables (`colspan` + `rowspan` from `gridSpan` and `vMerge`), images (embedded as data URIs by default, customisable handler), footnotes and endnotes (with backlinks), comments, content controls (`w:sdt`), symbol fonts (Wingdings / Webdings / Symbol via dingbat-to-unicode), legacy hyperlink complex fields (`w:fldChar`), checkbox form fields (SDT `wordml:checkbox` and `FORMCHECKBOX`) rendered as `` in HTML and `[x]` / `[ ]` in Markdown.
- Mammoth-compatible style-mapping DSL: `p[style-name='Heading 1'] => h1:fresh`, `b => strong`, `r.Code => code`, `comment-reference => sup`, `highlight[color='yellow'] => mark`, `br[type='page'] => hr`, `table.Grid => table.fancy`, `=> !` for ignore. Supports `:fresh`, `:separator('text')` and backslash escape sequences (`\n`, `\r`, `\t`, `\\`, `\'`) inside string literals.
- `Converter` options: `idPrefix` (namespace HTML ids), `ignoreEmptyParagraphs`, `prettyPrint` (indented HTML), `ignoreHiddenText` (drop `w:vanish` "Hidden" runs, on by default), `transformDocument` (callback to rewrite the parsed tree before rendering), plus `Transforms` helpers (`paragraph`, `run`, `elementsOfType`, `elements`, `getDescendants`, `getDescendantsOfType`) for walking and rebuilding the document tree.
- `extractRawText` for plain-text indexing pipelines.
- `embedStyleMap` / `readEmbeddedStyleMap` for mammoth-compatible in-document style maps.
- CLI `bin/elephant-php` for `.docx → HTML/Markdown` from the terminal.
- **Safe by default** for untrusted input — see [Security defaults](#security-defaults).

See [`ROADMAP.md`](ROADMAP.md) for the residual gap with mammoth.js (track-changes deletion concatenation, DSL list matchers, OMML equations).

Installation
------------

[](#installation)

```
composer require endless-creativity/elephant-php
```

Requires PHP 8.2+.

Usage
-----

[](#usage)

### HTML

[](#html)

```
use EndlessCreativity\ElephantPhp\Converter;

$result = (new Converter())->convertToHtml('/path/to/file.docx');

echo $result->value;             // semantic HTML
foreach ($result->messages as $message) {
    fwrite(STDERR, "[{$message->type->value}] {$message->message}\n");
}
```

### Markdown

[](#markdown)

```
$result = (new Converter())->convertToMarkdown('/path/to/file.docx');

file_put_contents('article.md', $result->value);
```

### Plain text

[](#plain-text)

```
$result = (new Converter())->extractRawText('/path/to/file.docx');

// Paragraphs are separated by "\n\n", everything else just contributes
// its descendant text. Useful for indexing/search pipelines.
echo $result->value;
```

### Custom style map

[](#custom-style-map)

Mammoth's DSL is supported as a list of rule strings. Rules are tried in order; the first match wins. The default heading map (Heading 1..6 → h1..h6) is appended after your rules.

```
$converter = new Converter(styleMap: [
    "p[style-name='Aside'] => aside.callout",
    "p[style-name='Quote'] => blockquote > p:fresh",
    "r[style-name='Code'] => code",
    "comment-reference => sup",                    // opt in to comments
    "highlight[color='yellow'] => mark.yellow",
    "br[type='page'] => hr",
    "p[style-name='List Paragraph'] =>",           // silence common warning
]);

$html = $converter->convertToHtml('/path/to/file.docx')->value;
```

### Custom image handler

[](#custom-image-handler)

By default, images are embedded as `data:` URIs. Plug in your own `ImageHandler` to write to disk / S3 / a CDN / whatever, returning the final `` attributes.

```
use EndlessCreativity\ElephantPhp\Document\Image;
use EndlessCreativity\ElephantPhp\Image\ImageHandler;

$handler = new class implements ImageHandler {
    public function attributes(Image $image): array {
        $bytes = ($image->readBytes)();
        $hash = hash('sha256', $bytes);
        $ext = ['image/png' => 'png', 'image/jpeg' => 'jpg'][$image->contentType] ?? 'bin';
        $path = "uploads/{$hash}.{$ext}";
        file_put_contents(__DIR__ . "/public/{$path}", $bytes);
        return ['src' => "/{$path}"];
    }
};

$converter = new Converter(imageHandler: $handler);
```

### Transform the document before rendering

[](#transform-the-document-before-rendering)

`transformDocument` receives the parsed `Document` and returns a (possibly modified) `Document`. Combine with the `Transforms` helpers to walk and rebuild the tree without writing recursion by hand:

```
use EndlessCreativity\ElephantPhp\Document\Run;
use EndlessCreativity\ElephantPhp\Document\Transforms;

// Force every run to bold before rendering.
$converter = new Converter(
    transformDocument: Transforms::run(
        static fn (Run $r): Run => new Run(children: $r->children, isBold: true),
    ),
);
```

### Embedded style map (read / write)

[](#embedded-style-map-read--write)

Mammoth supports embedding the style map as a part of the docx itself under `mammoth/style-map`. Read / write round-trips:

```
// Write
$bytes = Converter::embedStyleMap('/in.docx', "p[style-name='Aside'] => p.aside");
file_put_contents('/out.docx', $bytes);

// Read
$rules = Converter::readEmbeddedStyleMap('/out.docx');
```

### CLI

[](#cli)

```
vendor/bin/elephant-php /path/to/file.docx                  # → HTML to stdout
vendor/bin/elephant-php --markdown /path/to/file.docx       # → Markdown to stdout
vendor/bin/elephant-php /path/to/file.docx out.html         # → HTML to file
```

Conversion warnings are written to `stderr` regardless of the output destination.

Security defaults
-----------------

[](#security-defaults)

The library is designed to be safe to point at untrusted `.docx`input out of the box. Three behaviours diverge from mammoth.js for this reason:

- **`r:link` images are not fetched.** Mammoth resolves `` via `fs.readFile` on the relationship target, which is attacker-controlled in any user-uploaded scenario and exposes SSRF, local file inclusion, and `phar://`deserialisation. elephant-php refuses to load `r:link` bytes; the path is preserved on the `Image` node so a `transformDocument`hook can decide what to do.
- **Hyperlinks with `javascript:` / `vbscript:` / `data:` schemes lose their `` wrapper.** The match is case-insensitive and tolerates leading whitespace and control characters (browsers do too). The link text still renders inline; only the executable wrapper is dropped.
- **XML with a `DOCTYPE` declaration is rejected**, and `loadXML`is called with `LIBXML_NONET`. Combined, this neutralises XXE and billion-laughs attacks: an entity that's never read can't be expanded. Real OOXML files never declare a DOCTYPE.

Always sanitise the produced HTML before injecting it into a page — the library escapes attribute values and text content, but it does not run a full HTML sanitiser, and downstream contexts may have stricter requirements (e.g., CSP that forbids inline ``).

Warnings on real-world documents
--------------------------------

[](#warnings-on-real-world-documents)

The following messages are **expected** and don't indicate malfunctions:

- `Unrecognised paragraph style: 'List Paragraph' (Style ID: ListParagraph)`— Word applies this to list items; the default style map only covers headings. Add `p[style-name='List Paragraph'] =>` to silence.
- `Unrecognised run style: 'FootnoteReference'` — same mechanism inside `footnotes.xml`.
- `Image of type image/bmp is unlikely to display in web browsers` — informational; the `` is still emitted.

Development
-----------

[](#development)

```
composer install
composer test     # Pest
composer stan     # PHPStan level 8
composer format   # Laravel Pint
```

Project guidance and porting conventions live in [`CLAUDE.md`](CLAUDE.md). Limitations and roadmap items vs mammoth in [`ROADMAP.md`](ROADMAP.md). Contributions welcome — see [`CONTRIBUTING.md`](CONTRIBUTING.md).

Attribution
-----------

[](#attribution)

elephant-php is an independent PHP port inspired by mammoth.js by Michael Williamson. It adopts mammoth's core philosophy (extract semantics, not formatting) and style-mapping DSL. It is not affiliated with or endorsed by the mammoth.js project.

License
-------

[](#license)

[BSD-2-Clause](LICENSE). Copyright © 2026 Endless Creativity (PHP port) and © 2013 Michael Williamson (mammoth.js, from which this work derives its algorithmic structure and test fixtures).

###  Health Score

44

—

FairBetter than 90% of packages

Maintenance95

Actively maintained with recent releases

Popularity19

Limited adoption so far

Community12

Small or concentrated contributor base

Maturity41

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 97.4% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~12 days

Total

6

Last Release

25d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/6038939?v=4)[Matteo Milesi](/maintainers/mattmilesi)[@mattmilesi](https://github.com/mattmilesi)

---

Top Contributors

[![mattmilesi](https://avatars.githubusercontent.com/u/6038939?v=4)](https://github.com/mattmilesi "mattmilesi (75 commits)")[![dependabot[bot]](https://avatars.githubusercontent.com/in/29110?v=4)](https://github.com/dependabot[bot] "dependabot[bot] (2 commits)")

---

Tags

docxdocx-parserllmmarkdownraghtmlconvertermarkdownworddocxofficeOOXMLdocument

###  Code Quality

TestsPest

Static AnalysisPHPStan

Code StyleLaravel Pint

Type Coverage Yes

### Embed Badge

![Health badge](/badges/endless-creativity-elephant-php/health.svg)

```
[![Health](https://phpackages.com/badges/endless-creativity-elephant-php/health.svg)](https://phpackages.com/packages/endless-creativity-elephant-php)
```

###  Alternatives

[phpoffice/phpword

PHPWord - A pure PHP library for reading and writing word processing documents (OOXML, ODF, RTF, HTML, PDF)

7.6k39.0M249](/packages/phpoffice-phpword)[gotenberg/gotenberg-php

A PHP client for interacting with Gotenberg, a developer-friendly API for converting numerous document formats into PDF files, and more!

3896.2M31](/packages/gotenberg-gotenberg-php)[aspose-cloud/aspose-words-cloud

Open, generate, edit, split, merge, compare and convert Word documents. Integrate Cloud API into your solutions to manipulate documents. Convert PDF to Word (DOC, DOCX, ODT, RTF and HTML) and in the opposite direction.

31177.3k](/packages/aspose-cloud-aspose-words-cloud)[paperdoc-dev/paperdoc-lib

A zero-dependency PHP library for generating, parsing and converting documents (PDF, HTML, CSV, DOCX)

1253.7k](/packages/paperdoc-dev-paperdoc-lib)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
