mwparserfromhell Documentation

repository·main·Indexed 21 days ago

https://github.com/earwig/mwparserfromhell

A Python package for parsing MediaWiki wikicode into a structured syntax tree. It allows developers to inspect, manipulate, and reconstruct MediaWiki markup using the wikicode module. The library includes a fast compiled C tokenizer extension and provides tools for filtering templates, handling nested structures, and integrating with Pywikibot or the MediaWiki API.

Tokens
10.9K
Snippets
44
Records
53
Agent score
75%

What's inside mwparserfromhell

  1. Understand the limitations of the direct source code interface

    main

    Unlike the MediaWiki engine which generates HTML and resolves template contents, mwparserfromhell acts as a direct interface to the raw source code only. This leads to the following behaviors:

    • No Template Transclusion Awareness: The parser cannot see the contents of a template. If a template contains a closing tag (e.g., {{end-bold}} containing </b>), the parser will not recognize it and may treat the preceding tag as unfinished.
    • Link Adjacency: Templates placed immediately after external links (e.g., http://example.com{{foo}}) are treated as part of the link itself.
    • XML Tag Treatment: Any text that resembles an XML tag is treated as a tag, regardless of whether it is a recognized name, because valid tag names depend on specific MediaWiki extensions.
    • Namespace and Link Limitations:
      • Localized namespace names are not recognized; for example, [[File:...]] is treated as a standard wikilink.
      • Word-ending links (linktrail) are not supported due to their language-specific nature.
  2. Explore mwparserfromhell subpackages

    main

    The library is organized into several specialized subpackages that handle different aspects of the parsing process:

    • mwparserfromhell.nodes: Contains the various node types that make up the parsed wikitext tree.
    • mwparserfromhell.parser: Contains the core parsing logic used to transform text into nodes.
    • mwparserfromhell.smart_list: Provides specialized list structures used internally for managing parsed elements.
  3. How the Wikicode object works

    main

    A mwparserfromhell.Wikicode object acts as a container for the parsed MediaWiki syntax. It inherits string-like behavior (e.g., str(wikicode) returns the original text) but allows for structural manipulation of templates, links, and formatting.

    Key capabilities include:

    • Filtering: Use .filter_templates() to find all template nodes.
    • Template Access: Templates have .name and .params attributes.
    • Parameter Retrieval: Use .get(key_or_index) to access specific template parameters.
    • Modification: Wikicode objects can be treated like lists, supporting methods like .append(), .insert(), .remove(), and .replace() to alter the content.
    import mwparserfromhell
    
    text = "I has a template! {{foo|bar|baz|eggs=spam}} See it?"
    wikicode = mwparserfromhell.parse(text)
    
    templates = wikicode.filter_templates()
    foo = templates[0]
    print(foo.name)          # 'foo'
    print(foo.params)       # ['bar', 'baz', 'eggs=spam']
    print(foo.get("eggs").value) # 'spam'
  4. Handle nested templates and recursion

    main

    mwparserfromhell handles nested structures automatically. When calling filter_templates(), the method returns all templates found at all levels of nesting.

    If you want to avoid automatic recursion and instead traverse the tree manually, pass recursive=False to filter_templates(). This allows you to inspect the contents of a node (which are themselves Wikicode objects) and call filter_templates() on them individually.

    # Recursive behavior (default)
    text = "{{foo|{{bar}}={{baz|{{spam}}}}}}"
    print(mwparserfromhell.parse(text).filter_templates())
    # Output: ['{{foo|{{bar}}={{baz|{{spam}}}}}}', '{{bar}}', '{{baz|{{spam}}}}', '{{spam}}']
    
    # Manual traversal
    code = mwparserfromhell.parse("{{foo|this {{includes a|template}}}}")
    # Get only the top-level template
    top_level = code.filter_templates(recursive=False)[0]
    # Access the nested content inside the parameter
    inner_content = top_level.get(1).value
    # Parse the inner content manually
    print(inner_content.filter_templates()[0])
  5. Understand mwparserfromhell limitations

    main

    Because mwparserfromhell acts as a direct interface to the source code rather than the rendered HTML, users should be aware of several limitations:

    1. Transclusion Blindness: It cannot see syntax elements produced inside a template. If a template contains a closing tag like `</b

    `, the parser may treat the preceding opening tag as unclosed.

    1. Link Adjacency: Templates immediately following an external link (e.g., http://example.com{{foo}}) may be incorrectly parsed as part of the link.
    2. Overlapping Syntax: When syntax elements cross over (e.g., {{echo|''Hello}}), the parser may treat the first construct as plain text.
      • Workaround: Use mwparserfromhell.parse(text, skip_style_tags=True) to treat text formatting tags (like '' and ''') as plain text to avoid confusion.
    3. Wiki-specific settings: It does not support Word-ending links or recognize localized namespace names (treating [[File:...]] as a standard wikilink).
  6. Handle overlapping syntax elements with skip_style_tags

    main

    When syntax elements overlap (e.g., {{echo|''Hello}}, world!''), the parser cannot represent this in a standard syntax tree and may treat the first construct as plain text.

    If you are primarily interested in structural elements and want to avoid this confusion with text formatting, you can use the skip_style_tags=True option in mwparserfromhell.parse(). This tells the parser to treat style tags like '' (italics) and ''' (bold) as plain text, allowing the rest of the syntax to be parsed correctly.

    # Example workaround for overlapping syntax
    import mwparserfromhell
    
    wikitext = "{{echo|''Hello}}, world!''"
    # Using skip_style_tags=True to prevent the parser from getting confused by the overlapping italics
    parsed = mwparserfromhell.parse(wikitext, skip_style_tags=True)
  7. Install the latest development version using uv

    main

    If you want to work with the latest development version using uv, clone the repository and sync the environment:

    git clone https://github.com/earwig/mwparserfromhell.git
    cd mwparserfromhell
    uv sync
    uv run python -c 'import mwparserfromhell; print(mwparserfromhell.__version__)'
  8. Install mwparserfromhell via pip

    main

    The easiest way to install the parser is via PyPI. This will install the latest release, which includes prebuilt wheels with a fast, compiled C tokenizer extension for most environments (Linux x86_64/arm64, macOS x86_64/arm64, and Windows x86/x86_64).

    To install the pure-Python implementation (the slower fallback) instead of building the C extension, set the WITH_EXTENSION=0 environment variable during installation.

    pip install mwparserfromhell
  9. Install mwparserfromhell

    main

    You can install the latest release of mwparserfromhell from PyPI using pip. Prebuilt wheels are available for most environments (Linux x86_64/arm64, macOS x86_64/arm64, and Windows x86/x86_64) which include a fast, compiled C tokenizer extension.

    If you are building from source and want to fall back to the slower pure-Python implementation because the C extension cannot be built, set the environment variable WITH_EXTENSION=0 during installation.

    pip install mwparserfromhell
  10. Modify Wikicode and templates

    main

    Wikicode objects are mutable and support several methods for altering markup:

    • Adding/Removing Parameters: Use .add(name, value) to add a parameter to a template, or .remove() to delete nodes.
    • Replacing Content: Use .replace(old_text, new_text) to swap specific strings within the Wikicode object.
    • List-like operations: Wikicode objects support .append(), .insert(), and .remove().
    • Matching names: Use the .matches(string) method on a template's name to perform case-insensitive and whitespace-agnostic comparisons.
    text = "{{cleanup}} '''Foo''' is a [[bar]]. {{uncategorized}}"
    code = mwparserfromhell.parse(text)
    
    for template in code.filter_templates():
        # Use .matches() for robust name comparison
        if template.name.matches("Cleanup") and not template.has("date"):
            template.add("date", "July 2012")
    
    # Replace a specific template string
    code.replace("{{uncategorized}}", "{{bar-stub}}")
    
    print(str(code))
    # {{cleanup|date=July 2012}} '''Foo''' is a [[bar]]. {{bar-stub}}
  11. Integrate mwparserfromhell with MediaWiki API via requests

    main

    If you are not using a specialized library, you can parse MediaWiki content by fetching it via the MediaWiki API using the requests library. Ensure you provide a User-Agent header and target the revisions property with rvprop=content to extract the raw wikitext. Pass the extracted content string to mwparserfromhell.parse().

    import mwparserfromhell
    import requests
    
    API_URL = "https://en.wikipedia.org/w/api.php"
    
    def parse(title):
        params = {
            "action": "query",
            "prop": "revisions",
            "rvprop": "content",
            "rvslots": "main",
            "rvlimit": 1,
            "titles": title,
            "format": "json",
            "formatversion": "2",
        }
        headers = {"User-Agent": "My-Bot-Name/1.0"}
        req = requests.get(API_URL, headers=headers, params=params)
        res = req.json()
        revision = res["query"]["pages"][0]["revisions"][0]
        text = revision["slots"]["main"]["content"]
        return mwparserfromhell.parse(text)
  12. Integrate mwparserfromhell with Pywikibot

    main

    To use mwparserfromhell with Pywikibot, retrieve the page content using Pywikibot's get() method and pass the resulting text string to mwparserfromhell.parse().

    import mwparserfromhell
    import pywikibot
    
    def parse(title):
        site = pywikibot.Site()
        page = pywikibot.Page(site, title)
        text = page.get()
        return mwparserfromhell.parse(text)