@khairold/xml-render 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +372 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +41 -0
- package/dist/index.js.map +1 -0
- package/dist/parser.d.ts +129 -0
- package/dist/parser.d.ts.map +1 -0
- package/dist/parser.js +321 -0
- package/dist/parser.js.map +1 -0
- package/dist/react/ErrorBoundary.d.ts +56 -0
- package/dist/react/ErrorBoundary.d.ts.map +1 -0
- package/dist/react/ErrorBoundary.js +69 -0
- package/dist/react/ErrorBoundary.js.map +1 -0
- package/dist/react/XmlRender.d.ts +62 -0
- package/dist/react/XmlRender.d.ts.map +1 -0
- package/dist/react/XmlRender.js +90 -0
- package/dist/react/XmlRender.js.map +1 -0
- package/dist/react/catalog.d.ts +99 -0
- package/dist/react/catalog.d.ts.map +1 -0
- package/dist/react/catalog.js +55 -0
- package/dist/react/catalog.js.map +1 -0
- package/dist/react/context.d.ts +66 -0
- package/dist/react/context.d.ts.map +1 -0
- package/dist/react/context.js +63 -0
- package/dist/react/context.js.map +1 -0
- package/dist/react/index.d.ts +35 -0
- package/dist/react/index.d.ts.map +1 -0
- package/dist/react/index.js +41 -0
- package/dist/react/index.js.map +1 -0
- package/dist/react-native/ErrorBoundary.d.ts +60 -0
- package/dist/react-native/ErrorBoundary.d.ts.map +1 -0
- package/dist/react-native/ErrorBoundary.js +84 -0
- package/dist/react-native/ErrorBoundary.js.map +1 -0
- package/dist/react-native/XmlRender.d.ts +62 -0
- package/dist/react-native/XmlRender.d.ts.map +1 -0
- package/dist/react-native/XmlRender.js +91 -0
- package/dist/react-native/XmlRender.js.map +1 -0
- package/dist/react-native/catalog.d.ts +100 -0
- package/dist/react-native/catalog.d.ts.map +1 -0
- package/dist/react-native/catalog.js +56 -0
- package/dist/react-native/catalog.js.map +1 -0
- package/dist/react-native/context.d.ts +66 -0
- package/dist/react-native/context.d.ts.map +1 -0
- package/dist/react-native/context.js +63 -0
- package/dist/react-native/context.js.map +1 -0
- package/dist/react-native/index.d.ts +35 -0
- package/dist/react-native/index.d.ts.map +1 -0
- package/dist/react-native/index.js +41 -0
- package/dist/react-native/index.js.map +1 -0
- package/dist/registry.d.ts +99 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +93 -0
- package/dist/registry.js.map +1 -0
- package/dist/types.d.ts +436 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +28 -0
- package/dist/types.js.map +1 -0
- package/package.json +60 -0
package/README.md
ADDED
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
# @khairold/xml-render
|
|
2
|
+
|
|
3
|
+
A type-safe XML-like tag parser and renderer framework for React and React Native. Parse structured content from text streams and render with custom components.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Type-safe parsing** - Define tags with Zod schemas, get full TypeScript inference
|
|
8
|
+
- **Streaming support** - Parse content progressively as it streams in
|
|
9
|
+
- **Platform agnostic** - Separate entry points for React and React Native
|
|
10
|
+
- **Error boundaries** - Individual segment errors don't crash the entire UI
|
|
11
|
+
- **Immutable by design** - All registry and catalog instances are frozen
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install @khairold/xml-render zod
|
|
17
|
+
# or
|
|
18
|
+
bun add @khairold/xml-render zod
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Peer dependencies:
|
|
22
|
+
- `zod` ^3.0.0 (for attribute schemas)
|
|
23
|
+
- `react` ^18.0.0
|
|
24
|
+
- `react-native` (optional, for React Native renderer)
|
|
25
|
+
|
|
26
|
+
## Quick Start
|
|
27
|
+
|
|
28
|
+
```tsx
|
|
29
|
+
import { z } from 'zod';
|
|
30
|
+
import { createRegistry, createParser } from '@khairold/xml-render';
|
|
31
|
+
import { createCatalog, XmlRender } from '@khairold/xml-render/react';
|
|
32
|
+
|
|
33
|
+
// 1. Define your tags with Zod schemas
|
|
34
|
+
const registry = createRegistry({
|
|
35
|
+
callout: {
|
|
36
|
+
schema: z.object({ type: z.enum(['info', 'warning', 'error']) }),
|
|
37
|
+
hasContent: true,
|
|
38
|
+
},
|
|
39
|
+
image: {
|
|
40
|
+
schema: z.object({ src: z.string(), alt: z.string().optional() }),
|
|
41
|
+
selfClosing: true,
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// 2. Create a parser
|
|
46
|
+
const parser = createParser(registry);
|
|
47
|
+
|
|
48
|
+
// 3. Create a component catalog
|
|
49
|
+
const catalog = createCatalog(registry, {
|
|
50
|
+
components: {
|
|
51
|
+
callout: ({ segment }) => (
|
|
52
|
+
<div className={`callout callout-${segment.attributes?.type}`}>
|
|
53
|
+
{segment.content}
|
|
54
|
+
</div>
|
|
55
|
+
),
|
|
56
|
+
image: ({ segment }) => (
|
|
57
|
+
<img src={segment.attributes?.src} alt={segment.attributes?.alt} />
|
|
58
|
+
),
|
|
59
|
+
},
|
|
60
|
+
textRenderer: ({ segment }) => <span>{segment.content}</span>,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// 4. Parse and render
|
|
64
|
+
const text = 'Hello <callout type="info">Important message!</callout> World';
|
|
65
|
+
const segments = parser.parse(text);
|
|
66
|
+
|
|
67
|
+
function App() {
|
|
68
|
+
return <XmlRender segments={segments} catalog={catalog} />;
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## API Reference
|
|
73
|
+
|
|
74
|
+
### Core Functions
|
|
75
|
+
|
|
76
|
+
#### `createRegistry(definitions)`
|
|
77
|
+
|
|
78
|
+
Creates an immutable tag registry from tag definitions.
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
import { z } from 'zod';
|
|
82
|
+
import { createRegistry } from '@khairold/xml-render';
|
|
83
|
+
|
|
84
|
+
const registry = createRegistry({
|
|
85
|
+
chart: {
|
|
86
|
+
schema: z.object({
|
|
87
|
+
type: z.enum(['bar', 'line', 'pie']),
|
|
88
|
+
title: z.string().optional(),
|
|
89
|
+
}),
|
|
90
|
+
hasContent: true, // Tag contains inner content (default: true)
|
|
91
|
+
selfClosing: false, // Tag must have closing tag (default: false)
|
|
92
|
+
},
|
|
93
|
+
image: {
|
|
94
|
+
schema: z.object({
|
|
95
|
+
src: z.string(),
|
|
96
|
+
alt: z.string().optional(),
|
|
97
|
+
}),
|
|
98
|
+
selfClosing: true, // Self-closing tag like <image />
|
|
99
|
+
hasContent: false,
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
**Registry methods:**
|
|
105
|
+
- `registry.tagNames` - Array of all registered tag names
|
|
106
|
+
- `registry.hasTag(name)` - Check if a tag is registered
|
|
107
|
+
- `registry.getTag(name)` - Get the definition for a tag
|
|
108
|
+
- `registry.validateAttributes(name, attrs)` - Validate attributes with Zod schema
|
|
109
|
+
- `registry.isSelfClosing(name)` - Check if tag is self-closing
|
|
110
|
+
- `registry.hasContent(name)` - Check if tag has content
|
|
111
|
+
|
|
112
|
+
#### `createParser(registry)`
|
|
113
|
+
|
|
114
|
+
Creates a parser instance bound to a registry.
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
import { createParser } from '@khairold/xml-render';
|
|
118
|
+
|
|
119
|
+
const parser = createParser(registry);
|
|
120
|
+
|
|
121
|
+
// Parse complete text
|
|
122
|
+
const segments = parser.parse('Hello <callout type="info">World</callout>');
|
|
123
|
+
// [
|
|
124
|
+
// { type: 'text', content: 'Hello ' },
|
|
125
|
+
// { type: 'callout', content: 'World', attributes: { type: 'info' } },
|
|
126
|
+
// ]
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
**Parser methods:**
|
|
130
|
+
- `parser.parse(text)` - Parse complete text into segments
|
|
131
|
+
- `parser.createState()` - Create initial state for streaming
|
|
132
|
+
- `parser.parseChunk(chunk, state)` - Parse streaming chunk
|
|
133
|
+
- `parser.finalize(state)` - Flush remaining buffer at stream end
|
|
134
|
+
|
|
135
|
+
### Streaming Usage
|
|
136
|
+
|
|
137
|
+
For real-time content streaming (e.g., LLM responses):
|
|
138
|
+
|
|
139
|
+
```ts
|
|
140
|
+
import { createParser, type ParserState } from '@khairold/xml-render';
|
|
141
|
+
|
|
142
|
+
const parser = createParser(registry);
|
|
143
|
+
|
|
144
|
+
// Initialize streaming state
|
|
145
|
+
let state = parser.createState();
|
|
146
|
+
let allSegments: Segments<typeof registry.definitions> = [];
|
|
147
|
+
|
|
148
|
+
// Process each chunk as it arrives
|
|
149
|
+
function onChunk(chunk: string) {
|
|
150
|
+
const result = parser.parseChunk(chunk, state);
|
|
151
|
+
|
|
152
|
+
// Update state for next chunk (immutable pattern)
|
|
153
|
+
state = result.state;
|
|
154
|
+
|
|
155
|
+
// Append complete segments
|
|
156
|
+
allSegments = [...allSegments, ...result.segments];
|
|
157
|
+
|
|
158
|
+
// Check if currently buffering a tag
|
|
159
|
+
if (result.isBuffering && result.bufferingTag) {
|
|
160
|
+
// Show loading placeholder for the buffering tag type
|
|
161
|
+
showPlaceholder(result.bufferingTag);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// When stream ends, finalize to flush any remaining content
|
|
166
|
+
function onStreamEnd() {
|
|
167
|
+
const finalSegments = parser.finalize(state);
|
|
168
|
+
allSegments = [...allSegments, ...finalSegments];
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### React Renderer
|
|
173
|
+
|
|
174
|
+
```tsx
|
|
175
|
+
import { createCatalog, XmlRender, XmlRenderProvider } from '@khairold/xml-render/react';
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
#### `createCatalog(registry, options)`
|
|
179
|
+
|
|
180
|
+
Creates a component catalog mapping tag types to renderers.
|
|
181
|
+
|
|
182
|
+
```tsx
|
|
183
|
+
const catalog = createCatalog(registry, {
|
|
184
|
+
components: {
|
|
185
|
+
// Each component receives { segment, index } props
|
|
186
|
+
callout: ({ segment }) => (
|
|
187
|
+
<div className={`callout-${segment.attributes?.type}`}>
|
|
188
|
+
{segment.content}
|
|
189
|
+
</div>
|
|
190
|
+
),
|
|
191
|
+
chart: ({ segment }) => (
|
|
192
|
+
<ChartComponent
|
|
193
|
+
type={segment.attributes?.type}
|
|
194
|
+
title={segment.attributes?.title}
|
|
195
|
+
data={JSON.parse(segment.content)}
|
|
196
|
+
/>
|
|
197
|
+
),
|
|
198
|
+
},
|
|
199
|
+
// Optional: custom text renderer (default: <span>{content}</span>)
|
|
200
|
+
textRenderer: ({ segment }) => <MarkdownText>{segment.content}</MarkdownText>,
|
|
201
|
+
});
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
#### `<XmlRender>` Component
|
|
205
|
+
|
|
206
|
+
Renders an array of segments using the catalog.
|
|
207
|
+
|
|
208
|
+
```tsx
|
|
209
|
+
// Option 1: Pass catalog directly
|
|
210
|
+
<XmlRender segments={segments} catalog={catalog} />
|
|
211
|
+
|
|
212
|
+
// Option 2: Use context provider
|
|
213
|
+
<XmlRenderProvider catalog={catalog}>
|
|
214
|
+
<XmlRender segments={segments} />
|
|
215
|
+
</XmlRenderProvider>
|
|
216
|
+
|
|
217
|
+
// With custom fallback for unknown segment types
|
|
218
|
+
<XmlRender
|
|
219
|
+
segments={segments}
|
|
220
|
+
catalog={catalog}
|
|
221
|
+
fallback={(segment, index) => (
|
|
222
|
+
<div>Unknown: {segment.type}</div>
|
|
223
|
+
)}
|
|
224
|
+
/>
|
|
225
|
+
|
|
226
|
+
// With custom error fallback
|
|
227
|
+
<XmlRender
|
|
228
|
+
segments={segments}
|
|
229
|
+
catalog={catalog}
|
|
230
|
+
errorFallback={(error, segmentType) => (
|
|
231
|
+
<div>Error rendering {segmentType}: {error.message}</div>
|
|
232
|
+
)}
|
|
233
|
+
/>
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### React Native Renderer
|
|
237
|
+
|
|
238
|
+
```tsx
|
|
239
|
+
import { createCatalog, XmlRender, XmlRenderProvider } from '@khairold/xml-render/react-native';
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
The React Native API is identical to React. The only differences are:
|
|
243
|
+
- Default text renderer uses `<Text>` instead of `<span>`
|
|
244
|
+
- Container uses `<View>` instead of `<div>`
|
|
245
|
+
- Error boundary styles use React Native `StyleSheet`
|
|
246
|
+
|
|
247
|
+
### Type Utilities
|
|
248
|
+
|
|
249
|
+
```ts
|
|
250
|
+
import {
|
|
251
|
+
type ParsedSegment,
|
|
252
|
+
type Segments,
|
|
253
|
+
type SegmentType,
|
|
254
|
+
type ParserState,
|
|
255
|
+
type InferAttributes,
|
|
256
|
+
isSegmentType,
|
|
257
|
+
} from '@khairold/xml-render';
|
|
258
|
+
|
|
259
|
+
// Get attribute type for a specific tag
|
|
260
|
+
type ChartAttrs = InferAttributes<typeof registry.definitions.chart>;
|
|
261
|
+
// { type: 'bar' | 'line' | 'pie'; title?: string }
|
|
262
|
+
|
|
263
|
+
// Type-safe segment type checking
|
|
264
|
+
const segment: ParsedSegment<typeof registry.definitions> = /* ... */;
|
|
265
|
+
if (isSegmentType(segment, 'chart', registry)) {
|
|
266
|
+
// segment.attributes is typed as ChartAttrs
|
|
267
|
+
console.log(segment.attributes.type);
|
|
268
|
+
}
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
## Example: Tag Definitions
|
|
272
|
+
|
|
273
|
+
### Callout (notification box)
|
|
274
|
+
|
|
275
|
+
```ts
|
|
276
|
+
const calloutDef = {
|
|
277
|
+
callout: {
|
|
278
|
+
schema: z.object({
|
|
279
|
+
type: z.enum(['info', 'warning', 'error']).default('info'),
|
|
280
|
+
}),
|
|
281
|
+
hasContent: true,
|
|
282
|
+
},
|
|
283
|
+
};
|
|
284
|
+
// Usage: <callout type="warning">Watch out!</callout>
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
### Table (markdown table)
|
|
288
|
+
|
|
289
|
+
```ts
|
|
290
|
+
const tableDef = {
|
|
291
|
+
table: {
|
|
292
|
+
schema: z.object({}),
|
|
293
|
+
hasContent: true,
|
|
294
|
+
},
|
|
295
|
+
};
|
|
296
|
+
// Usage: <table>| Col1 | Col2 |\n|---|---|\n| A | B |</table>
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
### Chart (data visualization)
|
|
300
|
+
|
|
301
|
+
```ts
|
|
302
|
+
const chartDef = {
|
|
303
|
+
chart: {
|
|
304
|
+
schema: z.object({
|
|
305
|
+
type: z.enum(['bar', 'line', 'pie']).default('bar'),
|
|
306
|
+
title: z.string().optional(),
|
|
307
|
+
}),
|
|
308
|
+
hasContent: true,
|
|
309
|
+
},
|
|
310
|
+
};
|
|
311
|
+
// Usage: <chart type="pie" title="Sales">{"labels":["Q1","Q2"],"data":[100,200]}</chart>
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
### Image (self-closing)
|
|
315
|
+
|
|
316
|
+
```ts
|
|
317
|
+
const imageDef = {
|
|
318
|
+
image: {
|
|
319
|
+
schema: z.object({
|
|
320
|
+
src: z.string(),
|
|
321
|
+
alt: z.string().optional(),
|
|
322
|
+
}),
|
|
323
|
+
selfClosing: true,
|
|
324
|
+
hasContent: false,
|
|
325
|
+
},
|
|
326
|
+
};
|
|
327
|
+
// Usage: <image src="photo.jpg" alt="A photo" />
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
## Platform-Specific Imports
|
|
331
|
+
|
|
332
|
+
```ts
|
|
333
|
+
// Core (parser + registry) - platform agnostic
|
|
334
|
+
import { createRegistry, createParser } from '@khairold/xml-render';
|
|
335
|
+
|
|
336
|
+
// React (web)
|
|
337
|
+
import { createCatalog, XmlRender } from '@khairold/xml-render/react';
|
|
338
|
+
|
|
339
|
+
// React Native (mobile)
|
|
340
|
+
import { createCatalog, XmlRender } from '@khairold/xml-render/react-native';
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
## Parsing Behavior
|
|
344
|
+
|
|
345
|
+
- **Unknown tags** pass through as literal text (not parsed)
|
|
346
|
+
- **Malformed/unclosed tags** fall back to text segments
|
|
347
|
+
- **XML entities** are decoded: `<` `>` `&` `"`
|
|
348
|
+
- **Attribute parsing** supports both single and double quotes
|
|
349
|
+
- **Case insensitive** tag matching
|
|
350
|
+
|
|
351
|
+
## Error Handling
|
|
352
|
+
|
|
353
|
+
Each segment is wrapped in an ErrorBoundary. If a component throws:
|
|
354
|
+
- **Development**: Shows error message with segment type
|
|
355
|
+
- **Production**: Renders a hidden/minimal fallback
|
|
356
|
+
|
|
357
|
+
```tsx
|
|
358
|
+
// Custom error handling
|
|
359
|
+
<XmlRender
|
|
360
|
+
segments={segments}
|
|
361
|
+
catalog={catalog}
|
|
362
|
+
errorFallback={(error, segmentType) => (
|
|
363
|
+
<div className="render-error">
|
|
364
|
+
Failed to render {segmentType}
|
|
365
|
+
</div>
|
|
366
|
+
)}
|
|
367
|
+
/>
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
## License
|
|
371
|
+
|
|
372
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @aura/xml-render - Core XML Parsing Library
|
|
3
|
+
*
|
|
4
|
+
* A type-safe XML-like tag parser and renderer framework.
|
|
5
|
+
* Supports both complete text parsing and streaming for real-time content.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* import { z } from 'zod';
|
|
10
|
+
* import { createRegistry, createParser } from '@aura/xml-render';
|
|
11
|
+
*
|
|
12
|
+
* // Define your tags with Zod schemas
|
|
13
|
+
* const registry = createRegistry({
|
|
14
|
+
* callout: {
|
|
15
|
+
* schema: z.object({ type: z.enum(['info', 'warning', 'error']) }),
|
|
16
|
+
* hasContent: true,
|
|
17
|
+
* },
|
|
18
|
+
* });
|
|
19
|
+
*
|
|
20
|
+
* // Create a parser and parse content
|
|
21
|
+
* const parser = createParser(registry);
|
|
22
|
+
* const segments = parser.parse('Hello <callout type="info">Important!</callout>');
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* @packageDocumentation
|
|
26
|
+
*/
|
|
27
|
+
export { createRegistry } from "./registry";
|
|
28
|
+
export { createParser } from "./parser";
|
|
29
|
+
export * from "./types";
|
|
30
|
+
export { isSegmentType } from "./types";
|
|
31
|
+
export type { TagDefinition, TagDefinitions, Registry, InferAttributes, SafeParseResult, } from "./registry";
|
|
32
|
+
export type { Parser, ParsedSegment, SegmentType, Segments, ParserState, StreamingParseResult, } from "./parser";
|
|
33
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAOH,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAG5C,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAOxC,cAAc,SAAS,CAAC;AAGxB,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAOxC,YAAY,EACV,aAAa,EACb,cAAc,EACd,QAAQ,EACR,eAAe,EACf,eAAe,GAChB,MAAM,YAAY,CAAC;AAEpB,YAAY,EACV,MAAM,EACN,aAAa,EACb,WAAW,EACX,QAAQ,EACR,WAAW,EACX,oBAAoB,GACrB,MAAM,UAAU,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @aura/xml-render - Core XML Parsing Library
|
|
3
|
+
*
|
|
4
|
+
* A type-safe XML-like tag parser and renderer framework.
|
|
5
|
+
* Supports both complete text parsing and streaming for real-time content.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* import { z } from 'zod';
|
|
10
|
+
* import { createRegistry, createParser } from '@aura/xml-render';
|
|
11
|
+
*
|
|
12
|
+
* // Define your tags with Zod schemas
|
|
13
|
+
* const registry = createRegistry({
|
|
14
|
+
* callout: {
|
|
15
|
+
* schema: z.object({ type: z.enum(['info', 'warning', 'error']) }),
|
|
16
|
+
* hasContent: true,
|
|
17
|
+
* },
|
|
18
|
+
* });
|
|
19
|
+
*
|
|
20
|
+
* // Create a parser and parse content
|
|
21
|
+
* const parser = createParser(registry);
|
|
22
|
+
* const segments = parser.parse('Hello <callout type="info">Important!</callout>');
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* @packageDocumentation
|
|
26
|
+
*/
|
|
27
|
+
// ============================================================================
|
|
28
|
+
// Core Functions
|
|
29
|
+
// ============================================================================
|
|
30
|
+
// Registry - Define your XML tags
|
|
31
|
+
export { createRegistry } from "./registry";
|
|
32
|
+
// Parser - Parse text into segments
|
|
33
|
+
export { createParser } from "./parser";
|
|
34
|
+
// ============================================================================
|
|
35
|
+
// Type Exports - Import from here for best DX
|
|
36
|
+
// ============================================================================
|
|
37
|
+
// Consolidated types file - preferred import location for types
|
|
38
|
+
export * from "./types";
|
|
39
|
+
// Re-export type guard helper
|
|
40
|
+
export { isSegmentType } from "./types";
|
|
41
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,+EAA+E;AAC/E,iBAAiB;AACjB,+EAA+E;AAE/E,kCAAkC;AAClC,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAE5C,oCAAoC;AACpC,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAExC,+EAA+E;AAC/E,8CAA8C;AAC9C,+EAA+E;AAE/E,gEAAgE;AAChE,cAAc,SAAS,CAAC;AAExB,8BAA8B;AAC9B,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC"}
|
package/dist/parser.d.ts
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* XML Parser for xml-render
|
|
3
|
+
*
|
|
4
|
+
* Creates a registry-aware parser that converts text containing XML-like tags
|
|
5
|
+
* into typed segments. Supports complete text parsing and streaming.
|
|
6
|
+
*/
|
|
7
|
+
import type { Registry, TagDefinitions, InferAttributes } from "./registry";
|
|
8
|
+
/**
|
|
9
|
+
* A parsed segment representing either plain text or a recognized tag
|
|
10
|
+
*/
|
|
11
|
+
export interface ParsedSegment<TDefs extends TagDefinitions = TagDefinitions, TType extends keyof TDefs | "text" = keyof TDefs | "text"> {
|
|
12
|
+
/** The segment type: 'text' or a registered tag name */
|
|
13
|
+
type: TType;
|
|
14
|
+
/** The content inside the tag, or the text content for 'text' segments */
|
|
15
|
+
content: string;
|
|
16
|
+
/** Attributes parsed from the tag (undefined for 'text' segments) */
|
|
17
|
+
attributes?: TType extends keyof TDefs ? InferAttributes<TDefs[TType]> : undefined;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Union of all segment types for a given registry
|
|
21
|
+
*/
|
|
22
|
+
export type SegmentType<TDefs extends TagDefinitions> = keyof TDefs | "text";
|
|
23
|
+
/**
|
|
24
|
+
* A segment array that can contain any valid segment for the registry
|
|
25
|
+
*/
|
|
26
|
+
export type Segments<TDefs extends TagDefinitions> = Array<ParsedSegment<TDefs, keyof TDefs | "text">>;
|
|
27
|
+
/**
|
|
28
|
+
* Parser state for handling incomplete/streaming content
|
|
29
|
+
*/
|
|
30
|
+
export interface ParserState {
|
|
31
|
+
/** Accumulated text buffer */
|
|
32
|
+
buffer: string;
|
|
33
|
+
/** Whether we're currently inside an unclosed component tag */
|
|
34
|
+
inComponent: boolean;
|
|
35
|
+
/** The tag name being processed, if any */
|
|
36
|
+
currentTag: string | null;
|
|
37
|
+
/** Attributes string for the current tag */
|
|
38
|
+
currentAttrs: string;
|
|
39
|
+
/** Index in buffer where current tag started */
|
|
40
|
+
tagStartIndex: number;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Result of parsing a streaming chunk
|
|
44
|
+
*/
|
|
45
|
+
export interface StreamingParseResult<TDefs extends TagDefinitions> {
|
|
46
|
+
/** Segments that are complete and can be rendered */
|
|
47
|
+
segments: Segments<TDefs>;
|
|
48
|
+
/** Updated parser state for next chunk */
|
|
49
|
+
state: ParserState;
|
|
50
|
+
/** Whether we're currently buffering (waiting for more data) */
|
|
51
|
+
isBuffering: boolean;
|
|
52
|
+
/** The type of tag being buffered, if any */
|
|
53
|
+
bufferingTag: keyof TDefs | null;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Parser interface returned by createParser
|
|
57
|
+
*/
|
|
58
|
+
export interface Parser<TDefs extends TagDefinitions> {
|
|
59
|
+
/**
|
|
60
|
+
* Parse a complete text string into segments.
|
|
61
|
+
* This is the main entry point for non-streaming use cases.
|
|
62
|
+
*
|
|
63
|
+
* @param text - The complete text to parse
|
|
64
|
+
* @returns Array of parsed segments in order
|
|
65
|
+
*/
|
|
66
|
+
parse(text: string): Segments<TDefs>;
|
|
67
|
+
/**
|
|
68
|
+
* Create initial parser state for streaming parsing.
|
|
69
|
+
*
|
|
70
|
+
* @returns Fresh parser state
|
|
71
|
+
*/
|
|
72
|
+
createState(): ParserState;
|
|
73
|
+
/**
|
|
74
|
+
* Parse a chunk of streaming text.
|
|
75
|
+
* Handles incomplete tags by buffering until complete.
|
|
76
|
+
*
|
|
77
|
+
* @param chunk - New text chunk to process
|
|
78
|
+
* @param state - Current parser state
|
|
79
|
+
* @returns Parse result with complete segments and updated state
|
|
80
|
+
*/
|
|
81
|
+
parseChunk(chunk: string, state: ParserState): StreamingParseResult<TDefs>;
|
|
82
|
+
/**
|
|
83
|
+
* Finalize parsing, returning any remaining buffered content as text.
|
|
84
|
+
* Call this when streaming is complete.
|
|
85
|
+
*
|
|
86
|
+
* @param state - Current parser state
|
|
87
|
+
* @returns Final segments including any buffered content
|
|
88
|
+
*/
|
|
89
|
+
finalize(state: ParserState): Segments<TDefs>;
|
|
90
|
+
/** The registry used by this parser */
|
|
91
|
+
readonly registry: Registry<TDefs>;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Create a parser instance bound to a registry.
|
|
95
|
+
*
|
|
96
|
+
* The parser recognizes tags defined in the registry and converts them
|
|
97
|
+
* into typed segments. Unknown tags are treated as literal text.
|
|
98
|
+
*
|
|
99
|
+
* @example
|
|
100
|
+
* ```ts
|
|
101
|
+
* import { z } from 'zod';
|
|
102
|
+
* import { createRegistry, createParser } from '@aura/xml-render';
|
|
103
|
+
*
|
|
104
|
+
* const registry = createRegistry({
|
|
105
|
+
* callout: {
|
|
106
|
+
* schema: z.object({ type: z.enum(['info', 'warning', 'error']) }),
|
|
107
|
+
* hasContent: true,
|
|
108
|
+
* },
|
|
109
|
+
* image: {
|
|
110
|
+
* schema: z.object({ src: z.string(), alt: z.string().optional() }),
|
|
111
|
+
* selfClosing: true,
|
|
112
|
+
* },
|
|
113
|
+
* });
|
|
114
|
+
*
|
|
115
|
+
* const parser = createParser(registry);
|
|
116
|
+
*
|
|
117
|
+
* const segments = parser.parse('Hello <callout type="info">Important!</callout> World');
|
|
118
|
+
* // [
|
|
119
|
+
* // { type: 'text', content: 'Hello ' },
|
|
120
|
+
* // { type: 'callout', content: 'Important!', attributes: { type: 'info' } },
|
|
121
|
+
* // { type: 'text', content: ' World' },
|
|
122
|
+
* // ]
|
|
123
|
+
* ```
|
|
124
|
+
*
|
|
125
|
+
* @param registry - The tag registry to use for parsing
|
|
126
|
+
* @returns A parser instance
|
|
127
|
+
*/
|
|
128
|
+
export declare function createParser<TDefs extends TagDefinitions>(registry: Registry<TDefs>): Parser<TDefs>;
|
|
129
|
+
//# sourceMappingURL=parser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAE5E;;GAEG;AACH,MAAM,WAAW,aAAa,CAC5B,KAAK,SAAS,cAAc,GAAG,cAAc,EAC7C,KAAK,SAAS,MAAM,KAAK,GAAG,MAAM,GAAG,MAAM,KAAK,GAAG,MAAM;IAEzD,wDAAwD;IACxD,IAAI,EAAE,KAAK,CAAC;IACZ,0EAA0E;IAC1E,OAAO,EAAE,MAAM,CAAC;IAChB,qEAAqE;IACrE,UAAU,CAAC,EAAE,KAAK,SAAS,MAAM,KAAK,GAClC,eAAe,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,GAC7B,SAAS,CAAC;CACf;AAED;;GAEG;AACH,MAAM,MAAM,WAAW,CAAC,KAAK,SAAS,cAAc,IAAI,MAAM,KAAK,GAAG,MAAM,CAAC;AAE7E;;GAEG;AACH,MAAM,MAAM,QAAQ,CAAC,KAAK,SAAS,cAAc,IAAI,KAAK,CACxD,aAAa,CAAC,KAAK,EAAE,MAAM,KAAK,GAAG,MAAM,CAAC,CAC3C,CAAC;AA8BF;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,8BAA8B;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,+DAA+D;IAC/D,WAAW,EAAE,OAAO,CAAC;IACrB,2CAA2C;IAC3C,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,4CAA4C;IAC5C,YAAY,EAAE,MAAM,CAAC;IACrB,gDAAgD;IAChD,aAAa,EAAE,MAAM,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB,CAAC,KAAK,SAAS,cAAc;IAChE,qDAAqD;IACrD,QAAQ,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC;IAC1B,0CAA0C;IAC1C,KAAK,EAAE,WAAW,CAAC;IACnB,gEAAgE;IAChE,WAAW,EAAE,OAAO,CAAC;IACrB,6CAA6C;IAC7C,YAAY,EAAE,MAAM,KAAK,GAAG,IAAI,CAAC;CAClC;AAED;;GAEG;AACH,MAAM,WAAW,MAAM,CAAC,KAAK,SAAS,cAAc;IAClD;;;;;;OAMG;IACH,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IAErC;;;;OAIG;IACH,WAAW,IAAI,WAAW,CAAC;IAE3B;;;;;;;OAOG;IACH,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,GAAG,oBAAoB,CAAC,KAAK,CAAC,CAAC;IAE3E;;;;;;OAMG;IACH,QAAQ,CAAC,KAAK,EAAE,WAAW,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IAE9C,uCAAuC;IACvC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC;CACpC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,wBAAgB,YAAY,CAAC,KAAK,SAAS,cAAc,EACvD,QAAQ,EAAE,QAAQ,CAAC,KAAK,CAAC,GACxB,MAAM,CAAC,KAAK,CAAC,CA+Tf"}
|