wikioxide
Parse MediaWiki wikitext into typed domain models — locally, deterministically, and
without HTML: template calls with full parameter maps, categories with sort keys,
internal/external links, typed parser warnings and structural statistics. A local
replacement for action=parse with
prop=templates|categories|links|externallinks|parsewarnings.
Beyond the API-parity extraction it also exposes a heading/section tree and a talk-page discussion parser (threaded comments with authors and timestamps) — both reported as byte spans into the original wikitext, so tooling can slice or move exact source.
Status: pre-release. Parse-only extraction, template expansion, the section tree, and the discussion parser all work behind a 100%-covered test suite; a real-article parity report and release polish are next.
Parse a page as written (no expansion):
use ;
let parser = builder
.site_config
.build?;
let doc = parser.parse;
assert_eq!;
assert_eq!;
Or expand templates first (post-expansion sets), fetching sources through a TemplateSource:
use Arc;
use ;
let store = new;
store.insert;
let parser = builder
.site_config
.template_source
.build?;
if let Complete = parser.expand
For an async caller, build with .on_unresolved(UnresolvedPolicy::Halt): expand then returns
ExpandOutcome::MissingTemplates listing the pages to prefetch into the store before re-running.
Sections
ParsedDocument::sections() returns the nested heading tree. Every Section carries byte
spans into the original source — the heading line, the inner title, and the section body
(that heading through just before the next heading of the same or a higher level, subsections
included). Body spans are source-contiguous even across templates and comments, so a consumer
can slice or move a section verbatim.
use ;
let parser = builder
.site_config
.build?;
let doc = parser.parse;
let history = &doc.sections;
assert_eq!;
assert_eq!;
assert_eq!;
Talk-page discussions
Discussion::from_wikitext turns a saved talk page into signed, threaded comments — each with
its author, timestamp, indentation level, and source spans. It works on saved wikitext (where
~~~~ is already substituted into literal [[User:…]] links and a (UTC) timestamp), so no
expansion is needed; it is parameterised entirely by the SiteConfig (user namespaces and
localized month names) and threads replies by leading :/*/# depth.
use SiteConfig;
use Discussion;
let src = "\
== Requested move ==
Support. [[User:Alice|Alice]] 08:26, 8 May 2026 (UTC)
: Agree. [[User:Bob|Bob]] 09:10, 8 May 2026 (UTC)
";
let discussion = from_wikitext;
let reply = &discussion.comments;
assert_eq!;
assert_eq!;
assert_eq!; // a reply to the nomination (comment 0)
Pair it with sections() to group comments per nomination: a comment belongs to a section
when its span() falls within that section's body_span().
What "parity" means here
templates/categories/linksare post-expansion sets, like the API's:expandresolves templates (via a caller-suppliedTemplateSource) before extracting.parsegives the as-written view instead.- Supported parser functions:
#if,#ifeq,#ifexpr,#iferror,#ifexist,#switch,#expr(full ExprParser port),#time,#tag, plus the string functions and the common magic words/variables. Unsupported ones are left literal with a warning. - No Scribunto/Lua in v1:
{{#invoke:}}records theModule:page in the closure, yields a typed invocation and a warning. Lua-heavy pages therefore under-expand. - Warnings are a typed enum whose
Displaymatches MediaWiki'sparsewarningsstrings; the parity ones report their message key viaWarning::code().
Feature flags
| Flag | Default | Effect |
|---|---|---|
presets |
✓ | Vendored SiteConfig::{enwiki, ruwiki, generic}() + from_preset_json |
serde |
Serialize/Deserialize on domain types |
|
parity |
formatversion=2 mirror types for API-shaped JSON |
The section tree and discussion parser are always available; they need no feature flag.
Development
MediaWiki's parser-test corpus is GPL-2.0+ and is therefore never committed here; the harness downloads it on demand and runs an allowlisted subset as data.
License
WTFPL — Do What The Fuck You Want To Public License, Version 2.