brik 0.10.0

HTML tree manipulation library - a building block for HTML parsing and manipulation
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
# NsDefaults: Namespace Declaration Injection

NsDefaults provides automatic injection of missing namespace declarations into HTML documents, enabling the use of namespace-prefixed elements (like `svg:rect`) without manually adding `xmlns:` attributes.

## Overview

When working with namespaced content in HTML (particularly SVG or custom XML vocabularies), elements must reference declared namespace prefixes. NsDefaults automatically injects missing namespace declarations into the `<html>` tag, ensuring that your prefixed elements are properly recognized.

## The Problem

Consider this HTML with a prefixed SVG element:

```html
<html>
<body>
  <svg:rect width="100" height="50"/>
</body>
</html>
```

Without a namespace declaration, the `svg:` prefix is meaningless, and parsers won't recognize `<svg:rect>` as an SVG element. You would need to manually add:

```html
<html xmlns:svg="http://www.w3.org/2000/svg">
  ...
</html>
```

This becomes tedious when:

- Generating HTML programmatically
- Working with template systems
- Processing documents that use multiple namespaces
- Converting between different document formats

## The Solution

NsDefaults solves this by:

1. Parsing the HTML to locate the `<html>` tag
2. Detecting which namespace declarations are already present
3. Injecting only the missing declarations
4. Providing efficient integration with html5ever's parser

## Basic Usage

```rust
use brik::ns::NsDefaultsBuilder;
use brik::parse_html;

// HTML with prefixed elements but no xmlns declarations
let html = r#"<html><body><svg:rect width="100"/></body></html>"#;

// Configure which namespaces should be present
let ns_defaults = NsDefaultsBuilder::new()
    .namespace("svg", "http://www.w3.org/2000/svg")
    .from_string(html)?;

// Parse with the injected namespaces
let document = parse_html().from_iter(ns_defaults);
```

The resulting document will have `xmlns:svg="http://www.w3.org/2000/svg"` automatically added to the `<html>` tag.

## API

### NsDefaultsBuilder

The builder pattern separates namespace configuration from HTML processing:

```rust
use brik::ns::NsDefaultsBuilder;

let builder = NsDefaultsBuilder::new()
    .namespace("svg", "http://www.w3.org/2000/svg")
    .namespace("custom", "http://example.com/ns");
```

#### `new()` → NsDefaultsBuilder

Creates an empty builder with no registered namespaces.

#### `namespace(prefix, uri)` → NsDefaultsBuilder

Registers a namespace prefix mapping. Parameters:

- `prefix`: The namespace prefix (e.g., "svg", "custom")
- `uri`: The namespace URI or anything convertible to `Namespace`

Returns `self` for method chaining. If the same prefix is registered multiple times, the last registration wins.

#### `from_string(html)` → Result\<NsDefaults\>

Processes the HTML string and returns an `NsDefaults` instance. Errors if the HTML cannot be parsed or lacks an `<html>` tag.

### NsDefaults

The result of processing HTML with a builder. Provides three consumption paths:

#### Into\<String\>

Allocates and returns the complete modified HTML:

```rust
let html_string: String = ns_defaults.into();
```

#### From\<NsDefaults\> for StrTendril

Converts to html5ever's string type for use with `.one()`:

```rust
use html5ever::tendril::StrTendril;

let tendril: StrTendril = ns_defaults.into();
let document = parse_html().one(tendril);
```

#### IntoIterator (Recommended)

Yields string slices for efficient parsing with `.from_iter()`:

```rust
// Most efficient: no intermediate string allocation in NsDefaults
let document = parse_html().from_iter(ns_defaults);
```

## Advanced Usage

### Multiple Namespaces

Register as many namespaces as needed:

```rust
let ns_defaults = NsDefaultsBuilder::new()
    .namespace("svg", "http://www.w3.org/2000/svg")
    .namespace("math", "http://www.w3.org/1998/Math/MathML")
    .namespace("custom", "http://example.com/app")
    .from_string(html)?;
```

Declarations are added in alphabetical order by prefix for deterministic output.

### Existing Declarations

NsDefaults only adds missing declarations. If your HTML already has some namespace declarations, they won't be duplicated:

```rust
let html = r#"<html xmlns:svg="http://www.w3.org/2000/svg">
  <body><custom:widget/></body>
</html>"#;

// Only custom: will be added; svg: is already present
let ns_defaults = NsDefaultsBuilder::new()
    .namespace("svg", "http://www.w3.org/2000/svg")  // Already present
    .namespace("custom", "http://example.com/ns")     // Will be added
    .from_string(html)?;
```

Result:

```html
<html xmlns:svg="http://www.w3.org/2000/svg" xmlns:custom="http://example.com/ns">
```

### Overwriting Registrations

If you register the same prefix multiple times, the last value wins:

```rust
let ns_defaults = NsDefaultsBuilder::new()
    .namespace("svg", "http://example.com/wrong")
    .namespace("svg", "http://www.w3.org/2000/svg")  // This one is used
    .from_string(html)?;
```

### Using with html5ever Macros

Combine with html5ever's namespace macros:

```rust
use html5ever::ns;

let ns_defaults = NsDefaultsBuilder::new()
    .namespace("svg", ns!(svg))    // Uses html5ever's built-in SVG URI
    .namespace("html", ns!(html))
    .namespace("mathml", ns!(mathml))
    .from_string(html)?;
```

## Architecture

NsDefaults uses a **slice-based design** for efficiency:

1. **Parse Phase**: The HTML is parsed to locate the `<html>` tag and find the insertion point (just before the closing `>`).

2. **Storage Phase**: The original HTML is stored unchanged along with:
   - Tag position information (`HtmlTagInfo`)
   - The namespace declarations to add (as a single string)

3. **Output Phase**: When consumed, the HTML is provided in slices:
   - HTML before insertion point
   - Namespace declarations to add
   - HTML after insertion point

This design avoids unnecessary string allocations until the result is actually needed.

### Performance

The three consumption paths have different performance characteristics:

| Method | Allocation | Use Case |
|--------|-----------|----------|
| `IntoIterator` | None | Parsing with html5ever (recommended) |
| `Into<StrTendril>` | Single allocation | Parsing with `.one()` |
| `Into<String>` | Single allocation | String output needed |

For the best performance when feeding to html5ever, use the `IntoIterator` path:

```rust
let document = parse_html().from_iter(ns_defaults);  // Zero-copy from NsDefaults
```

**Note:** While `IntoIterator` avoids intermediate allocations in NsDefaults, html5ever's parser still copies the underlying string data into its internal representation. The performance benefit is that NsDefaults doesn't create an unnecessary intermediate concatenated string before feeding to the parser.

## Integration Examples

### Template Processing

Inject namespaces into template-generated HTML:

```rust
use brik::ns::NsDefaultsBuilder;
use brik::parse_html;
use brik::traits::*;

fn process_template(template_html: &str) -> Result<String, Box<dyn Error>> {
    // Inject template namespace
    let ns_defaults = NsDefaultsBuilder::new()
        .namespace("tmpl", "http://example.com/template")
        .from_string(template_html)?;

    // Parse and process
    let doc = parse_html().from_iter(ns_defaults);

    // Process template directives...

    Ok(doc.to_string())
}
```

### SVG with Custom Attributes

Combine standard SVG namespace with custom application namespace:

```rust
let html = r#"<html>
<body>
  <svg:svg width="200" height="100">
    <svg:rect app:id="widget-1" width="180" height="80"/>
  </svg:svg>
</body>
</html>"#;

let ns_defaults = NsDefaultsBuilder::new()
    .namespace("svg", "http://www.w3.org/2000/svg")
    .namespace("app", "http://example.com/app")
    .from_string(html)?;

let doc = parse_html().from_iter(ns_defaults);
```

### Document Format Conversion

When converting from other formats to HTML:

```rust
fn xml_to_html(xml: &str, namespaces: &[(String, String)])
    -> Result<Document, Box<dyn Error>>
{
    let mut builder = NsDefaultsBuilder::new();

    // Register all namespaces from the source format
    for (prefix, uri) in namespaces {
        builder = builder.namespace(prefix, uri.as_str());
    }

    let ns_defaults = builder.from_string(xml)?;
    Ok(parse_html().from_iter(ns_defaults))
}
```

## Common Patterns

### Reusable Builder

Create a configured builder for repeated use:

```rust
fn create_svg_builder() -> NsDefaultsBuilder {
    NsDefaultsBuilder::new()
        .namespace("svg", "http://www.w3.org/2000/svg")
        .namespace("xlink", "http://www.w3.org/1999/xlink")
}

// Use with multiple documents
let doc1 = parse_html().from_iter(
    create_svg_builder().from_string(html1)?
);
let doc2 = parse_html().from_iter(
    create_svg_builder().from_string(html2)?
);
```

### Display for Debugging

Use the `Display` implementation to see the processed HTML:

```rust
let ns_defaults = NsDefaultsBuilder::new()
    .namespace("svg", "http://www.w3.org/2000/svg")
    .from_string(html)?;

println!("Processed HTML:\n{}", ns_defaults);
```

### No-Op Processing

An empty builder passes HTML through unchanged:

```rust
let ns_defaults = NsDefaultsBuilder::new()
    .from_string(html)?;

// No namespaces added; HTML remains unchanged
assert_eq!(html, ns_defaults.to_string());
```

## Error Handling

`from_string()` returns `Result<NsDefaults, NsError>` and can fail if:

- The HTML cannot be parsed
- No `<html>` tag is found in the document

```rust
use brik::ns::{NsDefaultsBuilder, NsError};

let result = NsDefaultsBuilder::new()
    .namespace("svg", "http://www.w3.org/2000/svg")
    .from_string(invalid_html);

match result {
    Ok(ns_defaults) => {
        // Process successfully
    }
    Err(NsError::ParseError(msg)) => {
        eprintln!("Failed to parse HTML: {}", msg);
    }
}
```

## Limitations

### HTML Tag Required

NsDefaults requires an `<html>` tag in the document. Fragments without an `<html>` tag will fail:

```rust
// This will error - no <html> tag
let result = NsDefaultsBuilder::new()
    .from_string("<div>Hello</div>");

assert!(result.is_err());
```

### Attribute Preservation

All existing attributes on the `<html>` tag are preserved:

```html
<html lang="en" class="no-js">
```

After processing:

```html
<html lang="en" class="no-js" xmlns:svg="http://www.w3.org/2000/svg">
```

### Declaration Location

Namespace declarations are always added to the `<html>` tag, never to descendant elements. This follows XML best practices of declaring namespaces at the root element.

### Prefix-Only Checking

NsDefaults only checks if a prefix is already declared, not whether the URI matches. If `xmlns:svg` exists with a different URI, NsDefaults won't add it again or warn about the mismatch.

## See Also

- [namespaces.md]namespaces.md - Comprehensive guide to namespace support in brik
- [examples/ns_defaults.rs]../examples/ns_defaults.rs - Working example with integration test
- [html5ever documentation]https://docs.rs/html5ever/ - Parser integration details