How to Query Elements in Office Documents Using CSS-Like Selectors in OfficeCLI

OfficeCLI enables precise extraction of document elements from Word, Excel, and PowerPoint files by accepting CSS-style selector strings through its query command.

OfficeCLI is an open-source command-line interface and SDK for programmatic Office document manipulation. You can query elements in Office documents using CSS-like selectors to target specific paragraphs, table rows, or text runs without manually parsing Open XML. The resident server parses your selector and returns matching nodes as structured JSON objects.

How the Query Command Processes Selectors

When you send a query command, the resident server (ResidentServer.cs) immediately validates the presence of a selector argument. According to the source code at lines 2073–2079, the server checks for the selector parameter or its alias path, throwing an ArgumentException if neither is provided:

// ResidentServer.cs – lines 2073-2079
var selector = req.GetArgOrNull("selector") ?? req.GetArgOrNull("path") ?? "";
if (string.IsNullOrEmpty(selector))
    throw new ArgumentException("'query' requires a selector. Example: {\"command\": \"query\", \"selector\": \"row[Score>80]\"}");

Once validated, the selector string is passed to AttributeFilter.FilterSelector, which invokes the language-agnostic selector engine implemented in WordHandler.Selector.cs. This design allows the same CSS-like syntax to work across Word, Excel, and PowerPoint documents.

CSS-Like Selector Syntax Supported

The selector parser located in src/officecli/Handlers/Word/WordHandler.Selector.cs (lines 15–122) implements a subset of CSS selectors specifically tailored for Office document structures. The parsing logic uses ParseSelector, SplitChildCombinator, and ParseSingleSelector to tokenize your query before matching against the document DOM.

Element Names

Target specific Office XML elements using case-insensitive tag names:

  • p – paragraphs
  • run – text runs
  • row – table rows (Excel/Word tables)
  • cell – table cells

Attribute Tests

Filter elements by their XML attributes using bracket notation:

  • [attr=value] – exact string match
  • [attr>num] – numeric greater-than comparison
  • [attr<"text"] – string less-than comparison (lexical)

Pseudo-Selectors

Refine selections with functional pseudo-classes:

  • :contains("text") – elements containing specific text
  • :empty – elements with no child content
  • :no-alt – elements lacking alternative text attributes

Child Combinators

Navigate document hierarchies using the child combinator > (the parser specifically skips > characters inside brackets to avoid confusion with attribute operators):

// Example: table > row > cell selects direct cell children of rows within tables

Python SDK Integration

The Python wrapper (sdk/python/officecli.py) exposes the selector functionality by forwarding your dictionary directly to the resident process. Lines 24–26 of the SDK include selector in the top-level request schema alongside fields like index, after, before, and xpath:


# sdk/python/officecli.py – field list excerpt

# ... index, after, before, to, selector, text, mode, depth, part, xpath, action, …

This means any Python code can query documents by including the selector key in the request payload.

Practical Code Examples

Python SDK Example

Query Excel rows where the Score column exceeds 80:

import officecli

with officecli.create("sales.xlsx") as doc:
    result = doc.send({
        "command": "query",
        "selector": "row[Score>80]"
    })
    print("High scores:", result)

Direct CLI Usage

From the command line, use the --selector flag to extract paragraphs containing specific text:

officecli query --selector "p:contains('Important')" report.docx

Internal C# Implementation

For reference, the internal evaluation flow uses AttributeFilter.FilterSelector with a key resolver to map attribute names to values:

var (matches, warnings) = AttributeFilter.FilterSelector(
        selector,               // e.g., "row[Score>80]"
        _handler.Query,        // document-specific query delegate
        keyResolver);          // attribute name resolver

The MatchesParagraphAttrs and MatchesRunSelector methods then verify each candidate node against the parsed SelectorPart objects.

Summary

  • Argument validation occurs in ResidentServer.cs (lines 2073–2079), requiring either selector or path parameters.
  • Parser implementation resides in WordHandler.Selector.cs (lines 15–122), supporting element names, attribute tests, pseudo-selectors, and child combinators.
  • Cross-format support means selectors work identically across Word, Excel, and PowerPoint documents.
  • Python SDK at sdk/python/officecli.py (lines 24–26) forwards the selector field transparently to the resident server.
  • Return format consists of JSON objects representing matched nodes, or raw text for non-JSON payloads.

Frequently Asked Questions

What happens if I omit the selector argument in a query command?

The resident server raises an ArgumentException with a specific error message. In ResidentServer.cs at lines 2073–2079, the code checks for selector or path arguments, and if both are missing or empty, it throws: 'query' requires a selector. Example: {"command": "query", "selector": "row[Score>80]"}.

Which document elements can I target with CSS-like selectors?

You can target structural elements including paragraphs (p), text runs (run), table rows (row), and table cells (cell). The selector engine also supports attribute-based filtering like [attr=value] and pseudo-selectors such as :contains("text") and :empty.

How does the Python SDK handle selector queries?

The Python SDK (sdk/python/officecli.py) treats the selector as a standard request field. When you call doc.send() with a dictionary containing "command": "query" and a "selector" string, the SDK serializes this payload and pipes it to the resident server, returning the parsed JSON response directly to your Python code.

Can I use the child combinator > inside attribute values?

The parser in WordHandler.Selector.cs specifically handles this edge case by skipping > characters that appear inside brackets. This allows you to safely use comparison operators like [attr>80] without the parser misinterpreting the > as a child combinator.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →