Hello
\\First paragraph
\\Second paragraph
\\ About \\# zigquery [](https://github.com/OrlovEvgeny/zigquery/actions/workflows/ci.yml) [](https://github.com/OrlovEvgeny/zigquery/releases/latest) [](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, \\ \\
\\ \\ \\ ); 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/OrlovEvgeny/zigquery ``` If Zig reports an invalid fingerprint, make sure the repository owner is `OrlovEvgeny` and rerun `zig fetch --save` with the corrected URL. Then in your `build.zig`: ```zig const zigquery = b.dependency("zigquery", .{ .target = target, .optimize = optimize, }); module.addImport("zigquery", zigquery.module("zigquery")); ``` Supported Zig versions: **0.15.2** and **0.16.x**. ## 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 | | Logical lists | `:is(h1, h2)`, `:where(.note)` | Match any selector in a list | | `: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 = try sel.children(); const filtered_kids = try sel.childrenFiltered("p"); // Parents. const p = try sel.parent(); const all_parents = try sel.parents(); // Closest ancestor (or self) matching a selector. const wrapper = try sel.closest(".wrapper"); // Siblings. const sibs = try sel.siblings(); const next_el = try sel.next(); const prev_all = try sel.prevAll(); ``` ### 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 = try items.first(); const last = try items.last(); const third = try items.eq(2); // zero-based const from_end = try items.eq(-1); // negative indexes from end const middle = try items.sliceRange(1, 3); // Boolean checks. const is_active = try 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"); try el.setAttr("target", "_blank"); el.removeAttr("rel"); // Classes. try el.addClass("highlight bold"); try el.removeClass("nav"); try 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. try div.appendHtml("appended
"); try div.prependHtml("prepended
"); // Insert around selection. const p = try doc.find("p"); try p.afterHtml("