1 /**
2 * @file printer/json.c
3 * @author Radek Krejci <rkrejci@cesnet.cz>
4 * @brief JSON printer for libyang data structure
5 *
6 * Copyright (c) 2015 CESNET, z.s.p.o.
7 *
8 * This source code is licensed under BSD 3-Clause License (the "License").
9 * You may not use this file except in compliance with the License.
10 * You may obtain a copy of the License at
11 *
12 * https://opensource.org/licenses/BSD-3-Clause
13 */
14
15 #include <stdlib.h>
16 #include <stdio.h>
17 #include <string.h>
18 #include <assert.h>
19 #include <inttypes.h>
20
21 #include "common.h"
22 #include "printer.h"
23 #include "tree_data.h"
24 #include "resolve.h"
25 #include "tree_internal.h"
26
27 #define INDENT ""
28 #define LEVEL (level*2)
29
30 static int json_print_nodes(struct lyout *out, int level, const struct lyd_node *root, int withsiblings, int toplevel,
31 int options);
32
33 int
json_print_string(struct lyout * out,const char * text)34 json_print_string(struct lyout *out, const char *text)
35 {
36 unsigned int i, n;
37
38 if (!text) {
39 return 0;
40 }
41
42 ly_write(out, "\"", 1);
43 for (i = n = 0; text[i]; i++) {
44 const unsigned char ascii = text[i];
45 if (ascii < 0x20) {
46 /* control character */
47 n += ly_print(out, "\\u%.4X", ascii);
48 } else {
49 switch (ascii) {
50 case '"':
51 n += ly_print(out, "\\\"");
52 break;
53 case '\\':
54 n += ly_print(out, "\\\\");
55 break;
56 default:
57 ly_write(out, &text[i], 1);
58 n++;
59 }
60 }
61 }
62 ly_write(out, "\"", 1);
63
64 return n + 2;
65 }
66
67 static int
json_print_attrs(struct lyout * out,int level,const struct lyd_node * node,const struct lys_module * wdmod)68 json_print_attrs(struct lyout *out, int level, const struct lyd_node *node, const struct lys_module *wdmod)
69 {
70 struct lyd_attr *attr;
71 size_t len;
72 char *p;
73
74 LY_PRINT_SET;
75
76 if (wdmod) {
77 ly_print(out, "%*s\"%s:default\":\"true\"", LEVEL, INDENT, wdmod->name);
78 ly_print(out, "%s%s", node->attr ? "," : "", (level ? "\n" : ""));
79 }
80 for (attr = node->attr; attr; attr = attr->next) {
81 if (!attr->annotation) {
82 /* skip exception for the NETCONF's attribute since JSON is not defined for NETCONF */
83 continue;
84 }
85 if (lys_main_module(attr->annotation->module) != lys_main_module(node->schema->module)) {
86 ly_print(out, "%*s\"%s:%s\":", LEVEL, INDENT, attr->annotation->module->name, attr->name);
87 } else {
88 ly_print(out, "%*s\"%s\":", LEVEL, INDENT, attr->name);
89 }
90 /* leafref is not supported */
91 switch (attr->value_type) {
92 case LY_TYPE_BINARY:
93 case LY_TYPE_STRING:
94 case LY_TYPE_BITS:
95 case LY_TYPE_ENUM:
96 case LY_TYPE_INST:
97 case LY_TYPE_INT64:
98 case LY_TYPE_UINT64:
99 case LY_TYPE_DEC64:
100 json_print_string(out, attr->value_str);
101 break;
102
103 case LY_TYPE_INT8:
104 case LY_TYPE_INT16:
105 case LY_TYPE_INT32:
106 case LY_TYPE_UINT8:
107 case LY_TYPE_UINT16:
108 case LY_TYPE_UINT32:
109 case LY_TYPE_BOOL:
110 ly_print(out, "%s", attr->value_str[0] ? attr->value_str : "null");
111 break;
112
113 case LY_TYPE_IDENT:
114 p = strchr(attr->value_str, ':');
115 assert(p);
116 len = p - attr->value_str;
117 if (!strncmp(attr->value_str, attr->annotation->module->name, len)
118 && !attr->annotation->module->name[len]) {
119 /* do not print the prefix, it is the default prefix for this node */
120 json_print_string(out, ++p);
121 } else {
122 json_print_string(out, attr->value_str);
123 }
124 break;
125
126 case LY_TYPE_EMPTY:
127 ly_print(out, "[null]");
128 break;
129
130 default:
131 /* error */
132 LOGINT(node->schema->module->ctx);
133 return EXIT_FAILURE;
134 }
135
136 ly_print(out, "%s%s", attr->next ? "," : "", (level ? "\n" : ""));
137 }
138
139 LY_PRINT_RET(node->schema->module->ctx);
140 }
141
142 static int
json_print_leaf(struct lyout * out,int level,const struct lyd_node * node,int onlyvalue,int toplevel,int options)143 json_print_leaf(struct lyout *out, int level, const struct lyd_node *node, int onlyvalue, int toplevel, int options)
144 {
145 struct lyd_node_leaf_list *leaf = (struct lyd_node_leaf_list *)node, *iter;
146 const struct lys_type *type;
147 const char *schema = NULL, *p, *mod_name;
148 const struct lys_module *wdmod = NULL;
149 LY_DATA_TYPE datatype;
150 size_t len;
151
152 LY_PRINT_SET;
153
154 if ((node->dflt && (options & (LYP_WD_ALL_TAG | LYP_WD_IMPL_TAG))) ||
155 (!node->dflt && (options & LYP_WD_ALL_TAG) && lyd_wd_default(leaf))) {
156 /* we have implicit OR explicit default node */
157 /* get with-defaults module */
158 wdmod = ly_ctx_get_module(node->schema->module->ctx, "ietf-netconf-with-defaults", NULL, 1);
159 }
160
161 if (!onlyvalue) {
162 if (toplevel || !node->parent || nscmp(node, node->parent)) {
163 /* print "namespace" */
164 schema = lys_node_module(node->schema)->name;
165 ly_print(out, "%*s\"%s:%s\":%s", LEVEL, INDENT, schema, node->schema->name, (level ? " " : ""));
166 } else {
167 ly_print(out, "%*s\"%s\":%s", LEVEL, INDENT, node->schema->name, (level ? " " : ""));
168 }
169 }
170
171 datatype = leaf->value_type;
172 contentprint:
173 switch (datatype) {
174 case LY_TYPE_BINARY:
175 case LY_TYPE_STRING:
176 case LY_TYPE_BITS:
177 case LY_TYPE_ENUM:
178 case LY_TYPE_INST:
179 case LY_TYPE_INT64:
180 case LY_TYPE_UINT64:
181 case LY_TYPE_UNION:
182 case LY_TYPE_DEC64:
183 json_print_string(out, leaf->value_str);
184 break;
185
186 case LY_TYPE_INT8:
187 case LY_TYPE_INT16:
188 case LY_TYPE_INT32:
189 case LY_TYPE_UINT8:
190 case LY_TYPE_UINT16:
191 case LY_TYPE_UINT32:
192 case LY_TYPE_BOOL:
193 ly_print(out, "%s", leaf->value_str[0] ? leaf->value_str : "null");
194 break;
195
196 case LY_TYPE_IDENT:
197 p = strchr(leaf->value_str, ':');
198 assert(p);
199 len = p - leaf->value_str;
200 mod_name = leaf->schema->module->name;
201 if (!strncmp(leaf->value_str, mod_name, len) && !mod_name[len]) {
202 /* do not print the prefix, it is the default prefix for this node */
203 json_print_string(out, ++p);
204 } else {
205 json_print_string(out, leaf->value_str);
206 }
207 break;
208
209 case LY_TYPE_LEAFREF:
210 iter = (struct lyd_node_leaf_list *)leaf->value.leafref;
211 while (iter && (iter->value_type == LY_TYPE_LEAFREF)) {
212 iter = (struct lyd_node_leaf_list *)iter->value.leafref;
213 }
214 if (!iter) {
215 /* unresolved and invalid, but we can learn the correct type anyway */
216 type = lyd_leaf_type((struct lyd_node_leaf_list *)leaf);
217 if (!type) {
218 /* error */
219 return EXIT_FAILURE;
220 }
221 datatype = type->base;
222 } else {
223 datatype = iter->value_type;
224 }
225 goto contentprint;
226
227 case LY_TYPE_EMPTY:
228 ly_print(out, "[null]");
229 break;
230
231 case LY_TYPE_UNKNOWN:
232 if (leaf->value_str[0] == '\0') {
233 ly_write(out, "\"\"", 2);
234 break;
235 }
236 datatype = ((struct lys_node_leaf *)leaf->schema)->type.base;
237 goto contentprint;
238
239 default:
240 /* error */
241 LOGINT(node->schema->module->ctx);
242 return EXIT_FAILURE;
243 }
244
245 /* print attributes as sibling leafs */
246 if (!onlyvalue && (node->attr || wdmod)) {
247 if (schema) {
248 ly_print(out, ",%s%*s\"@%s:%s\":%s{%s", (level ? "\n" : ""), LEVEL, INDENT, schema, node->schema->name,
249 (level ? " " : ""), (level ? "\n" : ""));
250 } else {
251 ly_print(out, ",%s%*s\"@%s\":%s{%s", (level ? "\n" : ""), LEVEL, INDENT, node->schema->name,
252 (level ? " " : ""), (level ? "\n" : ""));
253 }
254 if (json_print_attrs(out, (level ? level + 1 : level), node, wdmod)) {
255 return EXIT_FAILURE;
256 }
257 ly_print(out, "%*s}", LEVEL, INDENT);
258 }
259
260 LY_PRINT_RET(node->schema->module->ctx);
261 }
262
263 static int
json_print_container(struct lyout * out,int level,const struct lyd_node * node,int toplevel,int options)264 json_print_container(struct lyout *out, int level, const struct lyd_node *node, int toplevel, int options)
265 {
266 const char *schema;
267
268 LY_PRINT_SET;
269
270 if (toplevel || !node->parent || nscmp(node, node->parent)) {
271 /* print "namespace" */
272 schema = lys_node_module(node->schema)->name;
273 ly_print(out, "%*s\"%s:%s\":%s{%s", LEVEL, INDENT, schema, node->schema->name, (level ? " " : ""), (level ? "\n" : ""));
274 } else {
275 ly_print(out, "%*s\"%s\":%s{%s", LEVEL, INDENT, node->schema->name, (level ? " " : ""), (level ? "\n" : ""));
276 }
277 if (level) {
278 level++;
279 }
280 if (node->attr) {
281 ly_print(out, "%*s\"@\":%s{%s", LEVEL, INDENT, (level ? " " : ""), (level ? "\n" : ""));
282 if (json_print_attrs(out, (level ? level + 1 : level), node, NULL)) {
283 return EXIT_FAILURE;
284 }
285 ly_print(out, "%*s}", LEVEL, INDENT);
286 ly_print(out, "%s%s", (node->child? "," : ""), (level ? "\n" : ""));
287 }
288 if (json_print_nodes(out, level, node->child, 1, 0, options)) {
289 return EXIT_FAILURE;
290 }
291 if (level) {
292 level--;
293 }
294 ly_print(out, "%*s}", LEVEL, INDENT);
295
296 LY_PRINT_RET(node->schema->module->ctx);
297 }
298
299 static int
json_print_leaf_list(struct lyout * out,int level,const struct lyd_node * node,int is_list,int toplevel,int options)300 json_print_leaf_list(struct lyout *out, int level, const struct lyd_node *node, int is_list, int toplevel, int options)
301 {
302 const char *schema = NULL;
303 const struct lyd_node *list = node;
304 int flag_empty = 0, flag_attrs = 0;
305
306 LY_PRINT_SET;
307
308 if (is_list && !list->child) {
309 /* empty, e.g. in case of filter */
310 flag_empty = 1;
311 }
312
313 if (toplevel || !node->parent || nscmp(node, node->parent)) {
314 /* print "namespace" */
315 schema = lys_node_module(node->schema)->name;
316 ly_print(out, "%*s\"%s:%s\":", LEVEL, INDENT, schema, node->schema->name);
317 } else {
318 ly_print(out, "%*s\"%s\":", LEVEL, INDENT, node->schema->name);
319 }
320
321 if (flag_empty) {
322 ly_print(out, "%s[]", (level ? " " : ""));
323 goto finish;
324 }
325 ly_print(out, "%s[%s", (level ? " " : ""), (level ? "\n" : ""));
326
327 if (!is_list && level) {
328 ++level;
329 }
330
331 while (list) {
332 if (is_list) {
333 /* list print */
334 if (level) {
335 ++level;
336 }
337 ly_print(out, "%*s{%s", LEVEL, INDENT, (level ? "\n" : ""));
338 if (level) {
339 ++level;
340 }
341 if (list->attr) {
342 ly_print(out, "%*s\"@\":%s{%s", LEVEL, INDENT, (level ? " " : ""), (level ? "\n" : ""));
343 if (json_print_attrs(out, (level ? level + 1 : level), list, NULL)) {
344 return EXIT_FAILURE;
345 }
346 if (list->child) {
347 ly_print(out, "%*s},%s", LEVEL, INDENT, (level ? "\n" : ""));
348 } else {
349 ly_print(out, "%*s}", LEVEL, INDENT);
350 }
351 }
352 if (json_print_nodes(out, level, list->child, 1, 0, options)) {
353 return EXIT_FAILURE;
354 }
355 if (level) {
356 --level;
357 }
358 ly_print(out, "%*s}", LEVEL, INDENT);
359 if (level) {
360 --level;
361 }
362 } else {
363 /* leaf-list print */
364 ly_print(out, "%*s", LEVEL, INDENT);
365 if (json_print_leaf(out, level, list, 1, toplevel, options)) {
366 return EXIT_FAILURE;
367 }
368 if (list->attr) {
369 flag_attrs = 1;
370 }
371 }
372 if (toplevel && !(options & LYP_WITHSIBLINGS)) {
373 /* if initially called without LYP_WITHSIBLINGS do not print other list entries */
374 break;
375 }
376 for (list = list->next; list && list->schema != node->schema; list = list->next);
377 if (list) {
378 ly_print(out, ",%s", (level ? "\n" : ""));
379 }
380 }
381
382 if (!is_list && level) {
383 --level;
384 }
385
386 ly_print(out, "%s%*s]", (level ? "\n" : ""), LEVEL, INDENT);
387
388 /* attributes */
389 if (!is_list && flag_attrs) {
390 if (schema) {
391 ly_print(out, ",%s%*s\"@%s:%s\":%s[%s", (level ? "\n" : ""), LEVEL, INDENT, schema, node->schema->name,
392 (level ? " " : ""), (level ? "\n" : ""));
393 } else {
394 ly_print(out, ",%s%*s\"@%s\":%s[%s", (level ? "\n" : ""), LEVEL, INDENT, node->schema->name,
395 (level ? " " : ""), (level ? "\n" : ""));
396 }
397 if (level) {
398 level++;
399 }
400 for (list = node; list; ) {
401 if (list->attr) {
402 ly_print(out, "%*s{%s", LEVEL, INDENT, (level ? " " : ""));
403 if (json_print_attrs(out, 0, list, NULL)) {
404 return EXIT_FAILURE;
405 }
406 ly_print(out, "%*s}", LEVEL, INDENT);
407 } else {
408 ly_print(out, "%*snull", LEVEL, INDENT);
409 }
410
411
412 for (list = list->next; list && list->schema != node->schema; list = list->next);
413 if (list) {
414 ly_print(out, ",%s", (level ? "\n" : ""));
415 }
416 }
417 if (level) {
418 level--;
419 }
420 ly_print(out, "%s%*s]", (level ? "\n" : ""), LEVEL, INDENT);
421 }
422
423 finish:
424 LY_PRINT_RET(node->schema->module->ctx);
425 }
426
427 static int
json_print_anydataxml(struct lyout * out,int level,const struct lyd_node * node,int toplevel,int options)428 json_print_anydataxml(struct lyout *out, int level, const struct lyd_node *node, int toplevel, int options)
429 {
430 struct lyd_node_anydata *any = (struct lyd_node_anydata *)node;
431 int is_object = 0;
432 char *buf;
433 const char *schema = NULL;
434
435 LY_PRINT_SET;
436
437 if (toplevel || !node->parent || nscmp(node, node->parent)) {
438 /* print "namespace" */
439 schema = lys_node_module(node->schema)->name;
440 ly_print(out, "%*s\"%s:%s\":", LEVEL, INDENT, schema, node->schema->name);
441 } else {
442 ly_print(out, "%*s\"%s\":", LEVEL, INDENT, node->schema->name);
443 }
444 if (level) {
445 level++;
446 }
447
448 switch (any->value_type) {
449 case LYD_ANYDATA_DATATREE:
450 is_object = 1;
451 ly_print(out, "%s{%s", (level ? " " : ""), (level ? "\n" : ""));
452 /* do not print any default values nor empty containers */
453 if (json_print_nodes(out, level, any->value.tree, 1, 0, LYP_WITHSIBLINGS | (options & ~LYP_NETCONF))) {
454 return EXIT_FAILURE;
455 }
456 break;
457 case LYD_ANYDATA_JSON:
458 if (level) {
459 ly_print(out, "\n");
460 }
461 if (any->value.str) {
462 ly_print(out, "%s", any->value.str);
463 }
464 if (level && (!any->value.str || (any->value.str[strlen(any->value.str) - 1] != '\n'))) {
465 /* do not print 2 newlines */
466 ly_print(out, "\n");
467 }
468 break;
469 case LYD_ANYDATA_XML:
470 lyxml_print_mem(&buf, any->value.xml, (level ? LYXML_PRINT_FORMAT | LYXML_PRINT_NO_LAST_NEWLINE : 0)
471 | LYXML_PRINT_SIBLINGS);
472 if (level) {
473 ly_print(out, " ");
474 }
475 json_print_string(out, buf);
476 free(buf);
477 break;
478 case LYD_ANYDATA_CONSTSTRING:
479 case LYD_ANYDATA_SXML:
480 if (level) {
481 ly_print(out, " ");
482 }
483 if (any->value.str) {
484 json_print_string(out, any->value.str);
485 } else {
486 ly_print(out, "\"\"");
487 }
488 break;
489 case LYD_ANYDATA_STRING:
490 case LYD_ANYDATA_SXMLD:
491 case LYD_ANYDATA_JSOND:
492 case LYD_ANYDATA_LYBD:
493 case LYD_ANYDATA_LYB:
494 /* other formats are not supported */
495 LOGINT(node->schema->module->ctx);
496 return EXIT_FAILURE;
497 }
498
499 /* print attributes as sibling leaf */
500 switch(node->schema->nodetype) {
501 case LYS_ANYXML:
502 if (level) {
503 level--;
504 }
505 if (is_object) {
506 ly_print(out, "%*s}", LEVEL, INDENT);
507 }
508 if (node->attr) {
509 if (schema) {
510 ly_print(out, ",%s%*s\"@%s:%s\":%s{%s", (level ? "\n" : ""), LEVEL, INDENT, schema, node->schema->name,
511 (level ? " " : ""), (level ? "\n" : ""));
512 } else {
513 ly_print(out, ",%s%*s\"@%s\":%s{%s", (level ? "\n" : ""), LEVEL, INDENT, node->schema->name,
514 (level ? " " : ""), (level ? "\n" : ""));
515 }
516 if (json_print_attrs(out, (level ? level + 1 : level), node, NULL)) {
517 return EXIT_FAILURE;
518 }
519 ly_print(out, "%*s}", LEVEL, INDENT);
520 }
521 break;
522 case LYS_ANYDATA:
523 if (node->attr) {
524 ly_print(out, ",%s%*s\"@\":%s{%s", (level ? "\n" : ""), LEVEL, INDENT, (level ? " " : ""), (level ? "\n" : ""));
525 if (json_print_attrs(out, (level ? level + 1 : level), node, NULL)) {
526 return EXIT_FAILURE;
527 }
528 ly_print(out, "%*s}", LEVEL, INDENT);
529 }
530 if (level) {
531 level--;
532 }
533 if (is_object) {
534 ly_print(out, "%s%*s}", (level ? "\n" : ""), LEVEL, INDENT);
535 }
536 break;
537 default:
538 LOGINT(node->schema->module->ctx);
539 return EXIT_FAILURE;
540 }
541
542 LY_PRINT_RET(node->schema->module->ctx);
543 }
544
545 static int
json_print_nodes(struct lyout * out,int level,const struct lyd_node * root,int withsiblings,int toplevel,int options)546 json_print_nodes(struct lyout *out, int level, const struct lyd_node *root, int withsiblings, int toplevel, int options)
547 {
548 int comma_flag = 0;
549 const struct lyd_node *node, *iter;
550
551 LY_PRINT_SET;
552
553 LY_TREE_FOR(root, node) {
554 if (lyd_node_should_print(node, options)) {
555 /* wd says to print */
556 switch (node->schema->nodetype) {
557 case LYS_RPC:
558 case LYS_ACTION:
559 case LYS_NOTIF:
560 case LYS_CONTAINER:
561 if (comma_flag) {
562 /* print the previous comma */
563 ly_print(out, ",%s", (level ? "\n" : ""));
564 }
565 if (json_print_container(out, level, node, toplevel, options)) {
566 return EXIT_FAILURE;
567 }
568 break;
569 case LYS_LEAF:
570 if (comma_flag) {
571 /* print the previous comma */
572 ly_print(out, ",%s", (level ? "\n" : ""));
573 }
574 if (json_print_leaf(out, level, node, 0, toplevel, options)) {
575 return EXIT_FAILURE;
576 }
577 break;
578 case LYS_LEAFLIST:
579 case LYS_LIST:
580 /* is it already printed? (root node is not) */
581 for (iter = node->prev; iter->next && node != root; iter = iter->prev) {
582 if (iter == node) {
583 continue;
584 }
585 if (iter->schema == node->schema) {
586 /* the list has alread some previous instance and therefore it is already printed */
587 break;
588 }
589 }
590 if (!iter->next || node == root) {
591 if (comma_flag) {
592 /* print the previous comma */
593 ly_print(out, ",%s", (level ? "\n" : ""));
594 }
595
596 /* print the list/leaflist */
597 if (json_print_leaf_list(out, level, node, node->schema->nodetype == LYS_LIST ? 1 : 0, toplevel, options)) {
598 return EXIT_FAILURE;
599 }
600 }
601 break;
602 case LYS_ANYXML:
603 case LYS_ANYDATA:
604 if (comma_flag) {
605 /* print the previous comma */
606 ly_print(out, ",%s", (level ? "\n" : ""));
607 }
608 if (json_print_anydataxml(out, level, node, toplevel, options)) {
609 return EXIT_FAILURE;
610 }
611 break;
612 default:
613 LOGINT(node->schema->module->ctx);
614 return EXIT_FAILURE;
615 }
616
617 comma_flag = 1;
618 }
619
620 if (!withsiblings) {
621 break;
622 }
623 }
624 if (root && level) {
625 ly_print(out, "\n");
626 }
627
628 LY_PRINT_RET(root ? root->schema->module->ctx : NULL);
629 }
630
631 int
json_print_data(struct lyout * out,const struct lyd_node * root,int options)632 json_print_data(struct lyout *out, const struct lyd_node *root, int options)
633 {
634 const struct lyd_node *node, *next;
635 int level = 0, action_input = 0;
636
637 LY_PRINT_SET;
638
639 if (options & LYP_FORMAT) {
640 ++level;
641 }
642
643 if (options & LYP_NETCONF) {
644 if (root->schema->nodetype != LYS_RPC) {
645 /* learn whether we are printing an action */
646 LY_TREE_DFS_BEGIN(root, next, node) {
647 if (node->schema->nodetype == LYS_ACTION) {
648 break;
649 }
650 LY_TREE_DFS_END(root, next, node);
651 }
652 } else {
653 node = root;
654 }
655
656 if (node && (node->schema->nodetype & (LYS_RPC | LYS_ACTION))) {
657 if (node->child && (node->child->schema->parent->nodetype == LYS_OUTPUT)) {
658 /* skip the container */
659 root = node->child;
660 } else if (node->schema->nodetype == LYS_ACTION) {
661 action_input = 1;
662 }
663 }
664 }
665
666 /* start */
667 ly_print(out, "{%s", (level ? "\n" : ""));
668
669 if (action_input) {
670 ly_print(out, "%*s\"yang:action\":%s{%s", LEVEL, INDENT, (level ? " " : ""), (level ? "\n" : ""));
671 if (level) {
672 ++level;
673 }
674 }
675
676 /* content */
677 if (json_print_nodes(out, level, root, options & LYP_WITHSIBLINGS, 1, options)) {
678 return EXIT_FAILURE;
679 }
680
681 if (action_input) {
682 if (level) {
683 --level;
684 }
685 ly_print(out, "%*s}%s", LEVEL, INDENT, (level ? "\n" : ""));
686 }
687
688 /* end */
689 ly_print(out, "}%s", (level ? "\n" : ""));
690
691 ly_print_flush(out);
692 LY_PRINT_RET(NULL);
693 }
694