An open API service indexing awesome lists of open source software.

https://github.com/OrlovEvgeny/zigquery

Zig HTML parser and CSS selector engine for DOM querying and manipulation
https://github.com/OrlovEvgeny/zigquery

dom-parser html parser zig zig-library zig-package

Last synced: 2 days ago
JSON representation

Zig HTML parser and CSS selector engine for DOM querying and manipulation

Awesome Lists containing this project

README

          

# zigquery

[![CI](https://github.com/OrlovEvgeny/zigquery/actions/workflows/ci.yml/badge.svg)](https://github.com/OrlovEvgeny/zigquery/actions/workflows/ci.yml)
[![Release](https://img.shields.io/github/v/release/OrlovEvgeny/zigquery)](https://github.com/OrlovEvgeny/zigquery/releases/latest)
[![Zig](https://img.shields.io/badge/Zig-0.15.2-f7a41d?logo=zig)](https://ziglang.org/)

jQuery-like HTML DOM manipulation library for Zig. Parse HTML, query elements with CSS selectors, traverse the tree, and manipulate the document
## Quick start

```zig
const std = @import("std");
const zq = @import("zigquery");

pub fn main() !void {
const allocator = std.heap.page_allocator;

var doc = try zq.Document.initFromSlice(allocator,
\\
\\
\\


\\

Hello


\\

First paragraph


\\

Second paragraph


\\ About
\\

\\
\\
);
defer doc.deinit();

// Find all paragraphs.
const paragraphs = try doc.find("p");
std.debug.print("Found {} paragraphs\n", .{paragraphs.len()});

// Get text content.
const title = try (try doc.find("h1")).text();
std.debug.print("Title: {s}\n", .{title});

// Read attributes.
const link = try doc.find("a.active");
const href = link.attr("href") orelse "";
std.debug.print("Link: {s}\n", .{href});
}
```

## Installation

Add zigquery as a dependency in your `build.zig.zon`:

```sh
zig fetch --save git+https://github.com/evgenyorlov/zigquery
```

Then in your `build.zig`:

```zig
const zigquery = b.dependency("zigquery", .{
.target = target,
.optimize = optimize,
});
module.addImport("zigquery", zigquery.module("zigquery"));
```

Requires **Zig 0.15.2** or later.

## CSS selectors

Supported selector syntax:

| Selector | Example | Description |
|---|---|---|
| Type | `div`, `p`, `a` | Match by tag name |
| Class | `.active`, `.foo.bar` | Match by class (compound supported) |
| ID | `#main` | Match by ID |
| Universal | `*` | Match any element |
| Attribute | `[href]`, `[type="text"]` | Attribute existence / value |
| Attribute operators | `[class~="foo"]`, `[lang\|="en"]`, `[href^="/"]`, `[src$=".png"]`, `[data*="val"]` | Includes, dash-match, prefix, suffix, substring |
| Descendant | `div p` | `p` anywhere inside `div` |
| Child | `div > p` | Direct child only |
| Adjacent sibling | `h1 + p` | Immediately after |
| General sibling | `h1 ~ p` | Any sibling after |
| Group | `h1, h2, h3` | Match any in the list |
| Negation | `:not(.hidden)` | Exclude matches |
| `:has()` | `div:has(> p)` | Parent has matching descendant |
| `:contains()` | `p:contains("hello")` | Element contains text |
| `:first-child`, `:last-child`, `:only-child` | `li:first-child` | Structural pseudo-classes |
| `:first-of-type`, `:last-of-type`, `:only-of-type` | `p:first-of-type` | Type-based structural pseudo-classes |
| `:nth-child()`, `:nth-last-child()` | `tr:nth-child(2n+1)` | Positional with `an+b` formula |
| `:nth-of-type()`, `:nth-last-of-type()` | `p:nth-of-type(odd)` | Type-positional |
| `:empty`, `:root` | `div:empty` | Content / root pseudo-classes |
| `:enabled`, `:disabled`, `:checked` | `input:enabled` | Form pseudo-classes |

## API overview

### Document

```zig
// Parse HTML into a document. All allocations use an internal arena.
var doc = try zq.Document.initFromSlice(allocator, html);
defer doc.deinit();

// Query from the document root.
const sel = try doc.find("div.content");

// Deep-clone the entire document.
var copy = try doc.clone(allocator);
defer copy.deinit();
```

### Selection — Traversal

```zig
const sel = try doc.find("div");

// Descendants matching a selector.
const links = try sel.find("a");

// Direct children.
const kids = sel.children();
const filtered_kids = try sel.childrenFiltered("p");

// Parents.
const p = sel.parent();
const all_parents = sel.parents();
const until = sel.parentsUntil(.{ .selector = "body" });

// Closest ancestor (or self) matching a selector.
const wrapper = try sel.closest(".wrapper");

// Siblings.
const sibs = sel.siblings();
const next_el = sel.next();
const prev_all = sel.prevAll();
const next_until = sel.nextUntil(.{ .selector = "hr" });
```

### Selection — Filtering

```zig
const items = try doc.find("li");

const active = try items.filter(".active");
const inactive = try items.not(".active");
const with_links = try items.has("a");

const first = items.first();
const last = items.last();
const third = items.eq(2); // zero-based
const from_end = items.eq(-1); // negative indexes from end
const middle = items.sliceRange(1, 3);

// Boolean checks.
const is_active = items.is(".active");
```

### Selection — Properties

```zig
const el = try doc.find("a.nav");

// Attributes.
const href = el.attr("href");
const title = el.attrOr("title", "default");
el.setAttr("target", "_blank");
el.removeAttr("rel");

// Classes.
el.addClass("highlight bold");
el.removeClass("nav");
el.toggleClass("active");
const has = el.hasClass("highlight");

// Content.
const inner = try el.html();
const outer = try zq.outerHtml(el);
const txt = try el.text();
const name = zq.nodeName(el);
```

### Selection — Manipulation

```zig
const div = try doc.find("div");

// Insert content.
div.appendHtml("

appended

");
div.prependHtml("

prepended

");

// Insert around selection.
const p = try doc.find("p");
p.afterHtml("


");
p.beforeHtml("");

// Replace and remove.
_ = p.replaceWithHtml("

replaced
");
_ = p.remove();
_ = div.empty(); // remove all children

// Set content.
div.setHtml("new content");
div.setText("plain text");

// Wrap / unwrap.
div.wrapHtml("");
div.unwrap();
```

### Selection — Iteration

```zig
const rows = try doc.find("tr");

// Iterator (idiomatic Zig).
var it = rows.iterator();
while (it.next()) |row| {
const cells = try row.find("td");
// ...
}

// Callback-based.
rows.each(struct {
fn f(i: usize, sel: zq.Selection) void {
_ = i;
_ = sel;
}
}.f);
```

### Selection — Set operations

```zig
const a = try doc.find(".foo");
const b = try doc.find(".bar");

const combined = try a.add(".bar");
const merged = a.addSelection(b);
const union_sel = a.@"union"(b);
const common = a.intersection(b);
```

## Running tests

```sh
zig build test
```

Inspired by Go's [goquery](https://github.com/PuerkitoBio/goquery) and, by extension, jQuery

## License
[MIT](LICENSE)