@uniweb/semantic-parser 1.1.4 → 1.1.6

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/reference/Text.js DELETED
@@ -1,188 +0,0 @@
1
- import React from 'react';
2
-
3
- /**
4
- * Text - A smart typography component for rendering content from semantic-parser
5
- *
6
- * Features:
7
- * - Handles single strings or arrays of paragraphs
8
- * - Smart semantic defaults for different content types
9
- * - Automatic filtering of empty content
10
- *
11
- * Security Model:
12
- * - Assumes content is ALREADY SANITIZED at the engine level
13
- * - Does NOT sanitize HTML (that's the engine's responsibility)
14
- * - Trusts the data it receives and renders it as-is
15
- *
16
- * @param {Object} props
17
- * @param {string|string[]} props.text - The content to render. Can be a string or an array of strings.
18
- * @param {string} [props.as='p'] - The tag to use for the wrapper or primary semantic element (e.g. 'h1', 'p', 'div').
19
- * @param {boolean} [props.html=true] - If true, renders content as HTML. If false, renders as plain text.
20
- * @param {string} [props.className] - Optional className to apply to the outer wrapper or individual elements.
21
- * @param {string} [props.lineAs] - For array inputs: tag to wrap each line. Defaults to 'div' for headings, 'p' for others.
22
- *
23
- * @example
24
- * // Simple paragraph (semantic default)
25
- * <Text text="Hello World" />
26
- *
27
- * // Explicit heading
28
- * <Text text="Hello World" as="h1" />
29
- *
30
- * // Multi-line heading
31
- * <Text text={["Welcome to", "Our Platform"]} as="h1" />
32
- *
33
- * // Multiple paragraphs (clean semantic output)
34
- * <Text text={["First paragraph", "Second paragraph"]} />
35
- *
36
- * // Rich HTML content (assumes already sanitized by engine)
37
- * <Text text={["Safe <strong>bold</strong> text", "With <em>emphasis</em>"]} />
38
- *
39
- * // Plain text when HTML is disabled
40
- * <Text text="No <strong>formatting</strong> here" html={false} />
41
- *
42
- * // Explicit div wrapper when needed
43
- * <Text text={["Item 1", "Item 2"]} as="div" lineAs="span" />
44
- */
45
- function Text({ text, as = 'p', html = true, className, lineAs }) {
46
- const isArray = Array.isArray(text);
47
- const Tag = as;
48
- const isHeading = as === 'h1' || as === 'h2' || as === 'h3' || as === 'h4' || as === 'h5' || as === 'h6';
49
-
50
- // Single string input
51
- if (!isArray) {
52
- if (!text || text.trim() === '') return null;
53
-
54
- if (html) {
55
- return (
56
- <Tag
57
- className={className}
58
- dangerouslySetInnerHTML={{ __html: text }}
59
- />
60
- );
61
- }
62
- return <Tag className={className}>{text}</Tag>;
63
- }
64
-
65
- // Array input - filter empty content first
66
- const filteredText = text.filter(
67
- (item) => typeof item === 'string' && item.trim() !== ''
68
- );
69
-
70
- if (filteredText.length === 0) {
71
- return null; // Don't render anything for empty arrays
72
- }
73
-
74
- // Determine the line wrapper tag with smart defaults
75
- const LineTag = lineAs || (isHeading ? 'div' : 'p');
76
-
77
- // Multi-line heading: wrap all lines in a single heading tag
78
- if (isHeading) {
79
- return (
80
- <Tag className={className}>
81
- {filteredText.map((line, i) => {
82
- if (html) {
83
- return (
84
- <LineTag
85
- key={i}
86
- dangerouslySetInnerHTML={{ __html: line }}
87
- />
88
- );
89
- }
90
- return <LineTag key={i}>{line}</LineTag>;
91
- })}
92
- </Tag>
93
- );
94
- }
95
-
96
- // Non-heading arrays: render each line as separate element
97
- return (
98
- <>
99
- {filteredText.map((line, i) => {
100
- if (html) {
101
- return (
102
- <LineTag
103
- key={i}
104
- className={className}
105
- dangerouslySetInnerHTML={{ __html: line }}
106
- />
107
- );
108
- }
109
- return (
110
- <LineTag key={i} className={className}>
111
- {line}
112
- </LineTag>
113
- );
114
- })}
115
- </>
116
- );
117
- }
118
-
119
- // ============================================================================
120
- // Semantic Wrapper Components - Thin wrappers around Text for common use cases
121
- // ============================================================================
122
-
123
- /**
124
- * H1 - Heading level 1 component
125
- * @param {Object} props - All Text props except 'as' (automatically set to 'h1')
126
- * @example
127
- * <H1 text="Main Title" />
128
- * <H1 text={["Multi-line", "Main Title"]} />
129
- */
130
- export const H1 = (props) => <Text {...props} as="h1" />;
131
-
132
- /**
133
- * H2 - Heading level 2 component
134
- * @param {Object} props - All Text props except 'as' (automatically set to 'h2')
135
- */
136
- export const H2 = (props) => <Text {...props} as="h2" />;
137
-
138
- /**
139
- * H3 - Heading level 3 component
140
- * @param {Object} props - All Text props except 'as' (automatically set to 'h3')
141
- */
142
- export const H3 = (props) => <Text {...props} as="h3" />;
143
-
144
- /**
145
- * H4 - Heading level 4 component
146
- * @param {Object} props - All Text props except 'as' (automatically set to 'h4')
147
- */
148
- export const H4 = (props) => <Text {...props} as="h4" />;
149
-
150
- /**
151
- * H5 - Heading level 5 component
152
- * @param {Object} props - All Text props except 'as' (automatically set to 'h5')
153
- */
154
- export const H5 = (props) => <Text {...props} as="h5" />;
155
-
156
- /**
157
- * H6 - Heading level 6 component
158
- * @param {Object} props - All Text props except 'as' (automatically set to 'h6')
159
- */
160
- export const H6 = (props) => <Text {...props} as="h6" />;
161
-
162
- /**
163
- * P - Paragraph component (explicitly semantic)
164
- * @param {Object} props - All Text props except 'as' (automatically set to 'p')
165
- * @example
166
- * <P text="A paragraph of content" />
167
- * <P text={["First paragraph", "Second paragraph"]} />
168
- */
169
- export const P = (props) => <Text {...props} as="p" />;
170
-
171
- /**
172
- * PlainText - Text component with HTML processing disabled
173
- * @param {Object} props - All Text props except 'html' (automatically set to false)
174
- * @example
175
- * <PlainText text="Display <strong>tags</strong> as literal text" />
176
- */
177
- export const PlainText = (props) => <Text {...props} html={false} />;
178
-
179
- /**
180
- * Div - Explicit div wrapper component
181
- * @param {Object} props - All Text props except 'as' (automatically set to 'div')
182
- * @example
183
- * <Div text={["Item 1", "Item 2"]} lineAs="span" />
184
- */
185
- export const Div = (props) => <Text {...props} as="div" />;
186
-
187
- // Export all components
188
- export default Text;
@@ -1,312 +0,0 @@
1
- /**
2
- * Path-based accessor for extracting values from parsed content
3
- */
4
-
5
- import { applyType, validateType } from './types.js';
6
-
7
- /**
8
- * Parse a path string into segments, handling array indices
9
- * @param {string} path - Path string (e.g., 'groups.main.body.imgs[0].url')
10
- * @returns {Array} Array of path segments
11
- */
12
- function parsePath(path) {
13
- const segments = [];
14
- const parts = path.split('.');
15
-
16
- for (const part of parts) {
17
- // Check for array index notation: key[0]
18
- const match = part.match(/^(.+?)\[(\d+)\]$/);
19
- if (match) {
20
- segments.push({ key: match[1], type: 'object' });
21
- segments.push({ index: parseInt(match[2], 10), type: 'array' });
22
- } else {
23
- segments.push({ key: part, type: 'object' });
24
- }
25
- }
26
-
27
- return segments;
28
- }
29
-
30
- /**
31
- * Get value at path from parsed content
32
- * @param {Object} parsed - Parsed content from parseContent()
33
- * @param {string} path - Path to value (e.g., 'groups.main.header.title')
34
- * @param {Object} options - Options for extraction
35
- * @param {*} options.defaultValue - Default value if path doesn't exist
36
- * @param {Function} options.transform - Transformation function to apply to value
37
- * @param {boolean} options.required - Throw error if value is missing
38
- * @param {string} options.type - Field type for automatic transformation
39
- * @param {boolean} options.treatEmptyAsDefault - Treat empty strings as missing
40
- * @returns {*} Value at path
41
- */
42
- function getByPath(parsed, path, options = {}) {
43
- const {
44
- defaultValue,
45
- transform,
46
- required = false,
47
- type,
48
- treatEmptyAsDefault = false,
49
- ...typeOptions
50
- } = options;
51
-
52
- if (!parsed || !path) {
53
- if (required) {
54
- throw new Error('Path is required');
55
- }
56
- return defaultValue;
57
- }
58
-
59
- const segments = parsePath(path);
60
- let current = parsed;
61
-
62
- for (let i = 0; i < segments.length; i++) {
63
- const segment = segments[i];
64
-
65
- if (current === null || current === undefined) {
66
- if (required) {
67
- throw new Error(`Required field missing at path: ${path}`);
68
- }
69
- return defaultValue;
70
- }
71
-
72
- if (segment.type === 'array') {
73
- if (!Array.isArray(current)) {
74
- if (required) {
75
- throw new Error(`Expected array at path segment ${i} in: ${path}`);
76
- }
77
- return defaultValue;
78
- }
79
- current = current[segment.index];
80
- } else {
81
- current = current[segment.key];
82
- }
83
- }
84
-
85
- let value = current !== undefined ? current : defaultValue;
86
-
87
- // Treat empty strings as missing if requested
88
- if (treatEmptyAsDefault && value === '') {
89
- value = defaultValue;
90
- }
91
-
92
- if (required && (value === undefined || value === null || value === '')) {
93
- throw new Error(`Required field missing at path: ${path}`);
94
- }
95
-
96
- // Apply type transformation if specified
97
- if (type && value !== undefined && value !== null) {
98
- value = applyType(value, type, typeOptions);
99
- }
100
-
101
- // Apply custom transform after type transformation
102
- return transform ? transform(value) : value;
103
- }
104
-
105
- /**
106
- * Extract multiple values using a schema
107
- * @param {Object} parsed - Parsed content from parseContent()
108
- * @param {Object} schema - Schema defining paths and transformations
109
- * @param {Object} options - Extraction options
110
- * @param {string} options.mode - Execution mode ('visual-editor' or 'build')
111
- * @returns {Object} Extracted values
112
- *
113
- * @example
114
- * const schema = {
115
- * title: {
116
- * path: 'groups.main.header.title',
117
- * type: 'plaintext',
118
- * maxLength: 60
119
- * },
120
- * image: {
121
- * path: 'groups.main.body.imgs[0].url',
122
- * type: 'image',
123
- * defaultValue: '/placeholder.jpg'
124
- * },
125
- * description: {
126
- * path: 'groups.main.body.paragraphs',
127
- * type: 'excerpt',
128
- * maxLength: 150
129
- * }
130
- * };
131
- * const data = extractBySchema(parsed, schema, { mode: 'visual-editor' });
132
- */
133
- function extractBySchema(parsed, schema, options = {}) {
134
- const { mode = 'visual-editor' } = options;
135
- const result = {};
136
- const validationResults = [];
137
-
138
- for (const [key, config] of Object.entries(schema)) {
139
- // Allow shorthand: key: 'path.to.value'
140
- if (typeof config === 'string') {
141
- result[key] = getByPath(parsed, config);
142
- } else {
143
- // Full config: { path, type, defaultValue, transform, required, ... }
144
- const { path, type, ...fieldOptions } = config;
145
-
146
- // Extract value
147
- result[key] = getByPath(parsed, path, { type, ...fieldOptions });
148
-
149
- // Validate if type specified and in build mode
150
- if (type && mode === 'build') {
151
- const rawValue = getByPath(parsed, path, {
152
- defaultValue: fieldOptions.defaultValue
153
- });
154
- const errors = validateType(rawValue, type, {
155
- ...fieldOptions,
156
- fieldName: key
157
- }, mode);
158
- validationResults.push(...errors);
159
- }
160
- }
161
- }
162
-
163
- // In build mode, log validation results
164
- if (mode === 'build' && validationResults.length > 0) {
165
- const errors = validationResults.filter(v => v.severity === 'error');
166
- const warnings = validationResults.filter(v => v.severity === 'warning');
167
-
168
- if (warnings.length > 0) {
169
- console.warn('Content validation warnings:');
170
- warnings.forEach(w => {
171
- console.warn(` [${w.field}] ${w.message}${w.autoFix ? ' (auto-fixed)' : ''}`);
172
- });
173
- }
174
-
175
- if (errors.length > 0) {
176
- console.error('Content validation errors:');
177
- errors.forEach(e => {
178
- console.error(` [${e.field}] ${e.message}`);
179
- });
180
- }
181
- }
182
-
183
- return result;
184
- }
185
-
186
- /**
187
- * Check if a path exists in parsed content
188
- * @param {Object} parsed - Parsed content
189
- * @param {string} path - Path to check
190
- * @returns {boolean} True if path exists and has a non-null/undefined value
191
- */
192
- function hasPath(parsed, path) {
193
- try {
194
- const value = getByPath(parsed, path);
195
- return value !== null && value !== undefined;
196
- } catch {
197
- return false;
198
- }
199
- }
200
-
201
- /**
202
- * Get multiple paths, return first that exists
203
- * @param {Object} parsed - Parsed content
204
- * @param {Array<string>} paths - Array of paths to try
205
- * @param {*} defaultValue - Default if none exist
206
- * @returns {*} First existing value or default
207
- */
208
- function getFirstExisting(parsed, paths, defaultValue = null) {
209
- for (const path of paths) {
210
- if (hasPath(parsed, path)) {
211
- return getByPath(parsed, path);
212
- }
213
- }
214
- return defaultValue;
215
- }
216
-
217
- /**
218
- * Extract values from array of items using same path
219
- * @param {Object} parsed - Parsed content
220
- * @param {string} arrayPath - Path to array
221
- * @param {string|Object} itemConfig - Path or config for each item
222
- * @returns {Array} Extracted values
223
- *
224
- * @example
225
- * // Get all item titles
226
- * mapArray(parsed, 'groups.items', 'header.title')
227
- *
228
- * // Get objects from each item
229
- * mapArray(parsed, 'groups.items', {
230
- * title: 'header.title',
231
- * text: { path: 'body.paragraphs', transform: p => p.join(' ') }
232
- * })
233
- */
234
- function mapArray(parsed, arrayPath, itemConfig) {
235
- const array = getByPath(parsed, arrayPath, { defaultValue: [] });
236
-
237
- if (!Array.isArray(array)) {
238
- return [];
239
- }
240
-
241
- return array.map(item => {
242
- if (typeof itemConfig === 'string') {
243
- return getByPath({ item }, `item.${itemConfig}`);
244
- } else {
245
- return extractBySchema({ item },
246
- Object.entries(itemConfig).reduce((acc, [key, config]) => {
247
- if (typeof config === 'string') {
248
- acc[key] = `item.${config}`;
249
- } else {
250
- acc[key] = { ...config, path: `item.${config.path}` };
251
- }
252
- return acc;
253
- }, {})
254
- );
255
- }
256
- });
257
- }
258
-
259
- /**
260
- * Validate content against schema without extracting
261
- * Useful for providing UI hints in visual editor
262
- * @param {Object} parsed - Parsed content
263
- * @param {Object} schema - Schema to validate against
264
- * @param {Object} options - Validation options
265
- * @param {string} options.mode - Execution mode ('visual-editor' or 'build')
266
- * @returns {Object} Validation results by field
267
- *
268
- * @example
269
- * const hints = validateSchema(parsed, schema);
270
- * // {
271
- * // title: [{ type: 'max_length', severity: 'info', message: '...' }],
272
- * // image: [{ type: 'required', severity: 'error', message: '...' }]
273
- * // }
274
- */
275
- function validateSchema(parsed, schema, options = {}) {
276
- const { mode = 'visual-editor' } = options;
277
- const results = {};
278
-
279
- for (const [key, config] of Object.entries(schema)) {
280
- if (typeof config === 'string') {
281
- continue; // No validation for shorthand
282
- }
283
-
284
- const { path, type, ...fieldOptions } = config;
285
-
286
- if (type) {
287
- const rawValue = getByPath(parsed, path, {
288
- defaultValue: fieldOptions.defaultValue
289
- });
290
-
291
- const errors = validateType(rawValue, type, {
292
- ...fieldOptions,
293
- fieldName: key
294
- }, mode);
295
-
296
- if (errors.length > 0) {
297
- results[key] = errors;
298
- }
299
- }
300
- }
301
-
302
- return results;
303
- }
304
-
305
- export {
306
- getByPath,
307
- extractBySchema,
308
- validateSchema,
309
- hasPath,
310
- getFirstExisting,
311
- mapArray
312
- };