Add ddsconf to generate md, rnc and xsd configuration documentation

Signed-off-by: Jeroen Koekkoek <jeroen@koekkoek.nl>
This commit is contained in:
Jeroen Koekkoek 2020-06-09 19:36:14 +02:00
parent 9c04099937
commit b25f10ff33
20 changed files with 5596 additions and 5087 deletions

View file

@ -12,6 +12,7 @@
set(CMAKE_INSTALL_TOOLSDIR "${CMAKE_INSTALL_DATADIR}/${PROJECT_NAME}/tools")
add_subdirectory(pubsub)
add_subdirectory(ddsls)
add_subdirectory(ddsconf)
if(BUILD_IDLC)
add_subdirectory(ddsperf)
endif()

View file

@ -0,0 +1,17 @@
#
# Copyright(c) 2020 ADLINK Technology Limited and others
#
# This program and the accompanying materials are made available under the
# terms of the Eclipse Public License v. 2.0 which is available at
# http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License
# v. 1.0 which is available at
# http://www.eclipse.org/org/documents/edl-v10.php.
#
# SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
#
add_executable(ddsconf ddsconf.c rnc.c md.c xsd.c)
target_link_libraries(ddsconf PRIVATE ddsc)
target_include_directories(ddsconf
PRIVATE
$<BUILD_INTERFACE:$<TARGET_PROPERTY:ddsc,INCLUDE_DIRECTORIES>>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>)

653
src/tools/ddsconf/ddsconf.c Normal file
View file

@ -0,0 +1,653 @@
/*
* Copyright(c) 2020 ADLINK Technology Limited and others
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License
* v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
*/
#include <assert.h>
#include <errno.h>
#include <getopt.h>
#include <limits.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "dds/ddsrt/heap.h"
#include "dds/ddsrt/io.h"
#include "dds/ddsrt/string.h"
#include "dds/ddsrt/misc.h"
#include "ddsconf.h"
/* configuration units */
#define UNIT(str, ...) { .name = str, __VA_ARGS__ }
#define DESCRIPTION(str) .description = str
#define PATTERN(str) .pattern = str
#define END_MARKER { NULL, NULL, NULL }
#include "dds/ddsi/ddsi_cfgunits.h"
/* undefine unit macros */
#undef UNIT
#undef DESCRIPTION
#undef PATTERN
#undef END_MARKER
/* configuration elements */
#define DEPRECATED(name) "|" name
#define MEMBER(name) /* drop */
#define MEMBEROF(parent,name) /* drop */
#define FUNCTIONS(...) /* drop */
#define DESCRIPTION(str) .description = str
#define RANGE(str) .range = str
#define UNIT(str) .unit = str
#define VALUES(...) .values = (const char *[]){ __VA_ARGS__, NULL }
#define NOMEMBER /* drop */
#define NOFUNCTIONS /* drop */
#define NOMETADATA { NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, NULL }
#define END_MARKER { NULL, NULL, NULL, 0, NULL, NULL, NOMETADATA }
#define ELEMENT(name, elems, attrs, multip, dflt, desc, ...) \
{ name, elems, attrs, multip, dflt, desc, { __VA_ARGS__ } }
#define MOVED(name, whereto) \
{ ">" name, NULL, NULL, 0, whereto, NULL, NOMETADATA }
#define EXPAND(macro, args) macro args /* Visual Studio */
#define NOP(name) \
EXPAND(ELEMENT, (name, NULL, NULL, 1, NULL, NULL, .type = "nop"))
#define BOOL(name, attrs, multip, dflt, ofst, funcs, desc, ...) \
EXPAND(ELEMENT, (name, NULL, attrs, multip, dflt, desc, .type = "bool", __VA_ARGS__))
#define INT(name, attrs, multip, dflt, ofst, funcs, desc, ...) \
EXPAND(ELEMENT, (name, NULL, attrs, multip, dflt, desc, .type = "int", __VA_ARGS__))
#define ENUM(name, attrs, multip, dflt, ofst, funcs, desc, ...) \
EXPAND(ELEMENT, (name, NULL, attrs, multip, dflt, desc, .type = "enum", __VA_ARGS__))
#define STRING(name, attrs, multip, dflt, ofst, funcs, desc, ...) \
EXPAND(ELEMENT, (name, NULL, attrs, multip, dflt, desc, .type = "string", __VA_ARGS__))
#define LIST(name, attrs, multip, dflt, ofst, funcs, desc, ...) \
EXPAND(ELEMENT, (name, NULL, attrs, multip, dflt, desc, .type = "list", __VA_ARGS__))
#define GROUP(name, elems, attrs, multip, ofst, funcs, desc) \
EXPAND(ELEMENT, (name, elems, attrs, multip, NULL, desc, .type = "group"))
#include "dds/ddsi/ddsi_cfgelems.h"
/* undefine element macros */
#undef DEPRECATED
#undef MEMBER
#undef MEMBEROF
#undef FUNCTIONS
#undef DESCRIPTION
#undef RANGE
#undef UNIT
#undef VALUES
#undef NOMEMBER
#undef NOFUNCTIONS
#undef NOMETADATA
#undef END_MARKER
#undef ELEMENT
#undef MOVED
#undef NOP
#undef BOOL
#undef INT
#undef ENUM
#undef STRING
#undef LIST
#undef GROUP
const char *schema(void) { return "config"; }
const char *url(void) { return "https://cdds.io/config"; }
const char *name(const struct cfgelem *elem)
{
if (elem->meta.name)
return (const char *)elem->meta.name;
return elem->name;
}
static char spaces[32];
void print(FILE *out, unsigned int cols, const char *fmt, ...)
{
va_list ap;
assert((size_t)cols < sizeof(spaces));
spaces[cols] = '\0';
fprintf(out, "%s", spaces);
spaces[cols] = ' ';
va_start(ap, fmt);
vfprintf(out, fmt, ap);
va_end(ap);
}
static void usage(const char *prog)
{
static const char fmt[] = "usage: %s [OPTIONS] -f FORMAT\n";
fprintf(stderr, fmt, prog);
}
static void help(const char *prog)
{
static const char fmt[] = "\
usage: %s -f FORMAT\n\
\n\
OPTIONS:\n\
-h print help message\n\
-f FORMAT output format. one of md, rnc or xsd\n\
-o FILENAME output file. specify - to use stdout\n\
";
fprintf(stdout, fmt, prog);
}
struct cfgelem *nextelem(const struct cfgelem *list, const struct cfgelem *elem)
{
const struct cfgelem *next = NULL;
if (list) {
const struct cfgelem *ce = list;
/* find next lexicographic element in list */
for (; ce && ce->name; ce++) {
if (ismoved(ce) || isdeprecated(ce) || isnop(ce))
continue;
if ((!elem || strcmp(ce->name, elem->name) > 0) &&
(!next || strcmp(ce->name, next->name) < 0))
next = ce;
}
}
return (struct cfgelem *)next;
}
struct cfgelem *firstelem(const struct cfgelem *list)
{
return nextelem(list, NULL);
}
const struct cfgunit *findunit(const struct cfgunit *units, const char *name)
{
for (const struct cfgunit *cu = units; cu->name; cu++) {
if (strcmp(cu->name, name) == 0) {
return cu;
}
}
return NULL;
}
int ismoved(const struct cfgelem *elem)
{
return elem && elem->name && elem->name[0] == '>';
}
int isdeprecated(const struct cfgelem *elem)
{
return elem && elem->name && elem->name[0] == '|';
}
int isgroup(const struct cfgelem *elem)
{
return strcmp(elem->meta.type, "group") == 0;
}
int isnop(const struct cfgelem *elem)
{
return strcmp(elem->meta.type, "nop") == 0;
}
int isbool(const struct cfgelem *elem)
{
return strcmp(elem->meta.type, "bool") == 0;
}
int isint(const struct cfgelem *elem)
{
return strcmp(elem->meta.type, "int") == 0;
}
int isstring(const struct cfgelem *elem)
{
return strcmp(elem->meta.type, "string") == 0;
}
int isenum(const struct cfgelem *elem)
{
return strcmp(elem->meta.type, "enum") == 0;
}
int islist(const struct cfgelem *elem)
{
return strcmp(elem->meta.type, "list") == 0;
}
int minimum(const struct cfgelem *elem)
{
switch (elem->multiplicity) {
case 0: /* special case, treat as-if 1 */
case 1: /* required if there is no default */
if (isgroup(elem))
return 0;
return (!elem->value);
default:
return 0;
}
}
int maximum(const struct cfgelem *elem)
{
switch (elem->multiplicity) {
case INT_MAX:
return 0;
case 0:
case 1:
return 1;
default:
return elem->multiplicity;
}
}
int haschildren(const struct cfgelem *elem)
{
int cnt = 0;
if (elem->children != NULL) {
for (const struct cfgelem *e = elem->children; e->name; e++) {
cnt += !(ismoved(e) || isdeprecated(e));
}
}
return cnt;
}
int hasattributes(const struct cfgelem *elem)
{
int cnt = 0;
if (elem->attributes != NULL) {
for (const struct cfgelem *e = elem->attributes; e->name; e++) {
cnt += !(ismoved(e) || isdeprecated(e));
}
}
return cnt;
}
/* remove element aliases "s/\|.*$//" */
static int sanitize_names(struct cfgelem *elem)
{
char *end;
if (!elem->name || ismoved(elem) || isdeprecated(elem))
return 0;
if ((end = strchr(elem->name, '|'))) {
assert(!elem->meta.name);
size_t len = (uintptr_t)end - (uintptr_t)elem->name;
if (!(elem->meta.name = ddsrt_malloc(len + 1)))
return -1;
memcpy(elem->meta.name, elem->name, len);
elem->meta.name[len] = '\0';
}
if (elem->children) {
for (struct cfgelem *ce = elem->children; ce->name; ce++) {
if (sanitize_names(ce) != 0)
return -1;
}
}
if (elem->attributes) {
for (struct cfgelem *ce = elem->attributes; ce->name; ce++) {
if (sanitize_names(ce) != 0)
return -1;
}
}
return 0;
}
static int generate_enum_pattern(struct cfgelem *elem)
{
char *pat;
const char **vals;
size_t cnt = 0, len = 0, pos = 0, size = 0;
assert(!elem->meta.pattern);
assert(elem->meta.values);
for (vals = elem->meta.values; *vals; vals++) {
cnt++;
size += strlen(*vals) + 2;
}
size += (cnt - 1) + 2 + 1;
if (!(pat = ddsrt_malloc(size)))
return -1;
pat[pos++] = '(';
for (vals = elem->meta.values; *vals; vals++) {
if (vals != elem->meta.values)
pat[pos++] = '|';
pat[pos++] = '"';
len = strlen(*vals);
assert(pos < size - len);
memcpy(pat+pos, *vals, len);
pos += len;
pat[pos++] = '"';
}
pat[pos++] = ')';
pat[pos++] = '\0';
assert(pos == size);
elem->meta.pattern = pat;
return 0;
}
static int generate_list_pattern(struct cfgelem *elem)
{
char *pat = NULL, *lst;
const char **vals, *val = (elem->value ? elem->value : "");
size_t cnt = 0, len = 0, pos = 0, size = 0;
assert(elem->meta.values != NULL);
for (vals = elem->meta.values; *vals != NULL; vals++) {
cnt++;
size += strlen(*vals);
}
size += (cnt - 1) + 2 + 1;
if (!(lst = ddsrt_malloc(size)))
return -1;
lst[pos++] = '(';
for (vals = elem->meta.values; *vals != NULL; vals++) {
if (vals != elem->meta.values)
lst[pos++] = '|';
len = strlen(*vals);
assert(pos < size - len);
memcpy(lst+pos, *vals, len);
pos += len;
}
lst[pos++] = ')';
lst[pos++] = '\0';
assert(pos == size);
if (strlen(val) != 0)
ddsrt_asprintf(&pat, "%s|(%s(,%s)*)", val, lst, lst);
else
ddsrt_asprintf(&pat, "(%s(,%s)*)|", lst, lst);
free(lst);
elem->meta.pattern = pat;
return pat ? 0 : -1;
}
/* generate patterns for enum and list elements */
int makepattern(struct cfgelem *elem, const struct cfgunit *units)
{
(void)units;
if (!elem->meta.pattern) {
if (isenum(elem))
return generate_enum_pattern(elem);
if (islist(elem))
return generate_list_pattern(elem);
}
return 0;
}
static int compare(struct cfgelem *a, struct cfgelem *b)
{
/* shallow compare (just pointer) is good enough for now */
if (a->children != b->children)
return -1;
if (a->attributes != b->attributes)
return -1;
if (strcmp(a->meta.type, b->meta.type) != 0)
return -1;
return 0;
}
static int duplicate(struct cfgelem *curs, struct cfgelem *elem)
{
if (curs == elem)
return 0;
if (curs->meta.flags & FLAG_DUPLICATE)
return 0;
if (elem->meta.flags & FLAG_DUPLICATE)
return 0;
if (strcmp(curs->name, elem->name) != 0)
return 0;
if (compare(curs, elem) != 0)
return -1;
curs->meta.flags |= FLAG_DUPLICATE;
return 0;
}
static int expand(struct cfgelem *curs, struct cfgelem *elem)
{
if (curs == elem)
return 0;
if (strcmp(curs->name, elem->name) != 0)
return 0;
curs->meta.flags &= ~FLAG_DUPLICATE;
curs->meta.flags |= FLAG_EXPAND;
elem->meta.flags |= FLAG_EXPAND;
return 0;
}
static int walk(
struct cfgelem *root,
struct cfgelem *elem,
int(*func)(struct cfgelem *, struct cfgelem *))
{
struct cfgelem *ce;
if (func(root, elem) == -1)
return -1;
ce = firstelem(root->children);
while (ce) {
if (walk(ce, elem, func) == -1)
return -1;
ce = nextelem(root->children, ce);
}
return 0;
}
static void mark(struct cfgelem *elem, struct cfgelem *root)
{
struct cfgelem *ce;
if (walk(root, elem, &duplicate) != 0)
walk(root, elem, &expand);
ce = firstelem(elem->children);
while (ce) {
mark(ce, root);
ce = nextelem(elem->children, ce);
}
}
#define BLOCK (1024)
static int
format(
char **strp,
size_t *lenp,
size_t *posp,
const char *src,
const char *(*xlat)(const char *, const char **))
{
char buf[2] = { '\0', '\0' };
size_t nonspc, pos;
const char *alt, *end, *ptr, *sub;
nonspc = pos = *posp;
for (ptr = src, end = ptr; *ptr; ptr = end) {
if (xlat && (alt = xlat(ptr, &end))) {
sub = alt;
} else {
buf[0] = *ptr;
end = ptr + 1;
sub = (const char *)buf;
}
for (size_t cnt = 0; sub[cnt]; cnt++) {
if (pos == *lenp) {
char *str;
size_t len = *lenp + BLOCK;
if (!(str = ddsrt_realloc(*strp, (len + 1) * sizeof(char)))) {
if (*strp)
ddsrt_free(*strp);
return -1;
}
*strp = str;
*lenp = len;
}
(*strp)[pos++] = sub[cnt];
}
}
if (nonspc != *posp && nonspc < pos)
pos = ++nonspc;
(*strp)[pos] = '\0';
(*posp) = pos;
return 0;
}
#define DFLTFMT "<p>The default value is: \"%s\".</p>"
int makedescription(
struct cfgelem *elem,
const struct cfgunit *units,
const char *(*xlat)(const char *, const char **))
{
if (elem->description) {
char *src = NULL;
char *dest = elem->meta.description;
const char *dflt = "";
size_t len = 0, pos = 0;
const struct cfgunit *unit;
if (isgroup(elem)) {
src = ddsrt_strdup(elem->description);
} else {
if (elem->value)
dflt = elem->value;
if (elem->meta.unit) {
unit = findunit(units, elem->meta.unit);
assert(unit);
ddsrt_asprintf(
&src, "%s\n%s\n"DFLTFMT, elem->description, unit->description, dflt);
} else {
ddsrt_asprintf(
&src, "%s\n"DFLTFMT, elem->description, dflt);
}
}
if (!src)
return -1;
format(&dest, &len, &pos, src, xlat);
ddsrt_free(src);
if (!dest)
return -1;
elem->meta.description = dest;
}
return 0;
}
static int init(struct cfgelem *root)
{
if (sanitize_names(root) != 0)
return -1;
mark(root, root);
return 0;
}
static void fini(struct cfgelem *elem)
{
struct cfgelem *ce;
if (!elem)
return;
if (elem->meta.name)
ddsrt_free(elem->meta.name);
if (elem->meta.title)
ddsrt_free(elem->meta.title);
if (elem->meta.pattern)
ddsrt_free(elem->meta.pattern);
if (elem->meta.description)
ddsrt_free(elem->meta.description);
elem->meta.name = NULL;
elem->meta.title = NULL;
elem->meta.pattern = NULL;
elem->meta.description = NULL;
ce = firstelem(elem->children);
while (ce) {
fini(ce);
ce = nextelem(elem->children, ce);
}
ce = firstelem(elem->attributes);
while (ce) {
fini(ce);
ce = nextelem(elem->attributes, ce);
}
}
int main(int argc, char *argv[])
{
int opt;
int code = EXIT_FAILURE;
FILE *out = NULL;
const char *file = "-";
enum { rnc, xsd, md } format = rnc;
while ((opt = getopt(argc, argv, "f:o:h")) != -1) {
switch (opt) {
case 'f':
if (strcmp(optarg, "rnc") == 0) {
format = rnc;
} else if (strcmp(optarg, "xsd") == 0) {
format = xsd;
} else if (strcmp(optarg, "md") == 0) {
format = md;
} else {
fprintf(stderr, "illegal output format: %s\n", optarg);
usage(argv[0]);
exit(EXIT_FAILURE);
}
break;
case 'h':
help(argv[0]);
exit(0);
break;
case 'o':
file = (const char *)optarg;
break;
default:
usage(argv[0]);
exit(EXIT_FAILURE);
}
}
if (init(cyclonedds_root_cfgelems) == -1) {
fprintf(stderr, "out of memory\n");
goto exit_failure;
}
DDSRT_WARNING_MSVC_OFF(4996)
if (strcmp(file, "-") == 0) {
out = stdout;
} else if ((out = fopen(file, "wb")) == NULL) {
fprintf(stderr, "cannot open %s for writing\n", file);
goto exit_failure;
}
DDSRT_WARNING_MSVC_ON(4996)
memset(spaces, ' ', sizeof(spaces));
switch (format) {
case rnc:
if (printrnc(out, cyclonedds_root_cfgelems, cfgunits) == 0)
code = EXIT_SUCCESS;
break;
case xsd:
if (printxsd(out, cyclonedds_root_cfgelems, cfgunits) == 0)
code = EXIT_SUCCESS;
break;
case md:
if (printmd(out, cyclonedds_root_cfgelems, cfgunits) == 0)
code = EXIT_SUCCESS;
break;
}
exit_failure:
fini(cyclonedds_root_cfgelems);
if (out != NULL && out != stdout)
fclose(out);
return code;
}

View file

@ -0,0 +1,80 @@
/*
* Copyright(c) 2020 ADLINK Technology Limited and others
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License
* v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
*/
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
struct cfgunit {
const char *name;
const char *description;
const char *pattern;
};
#define FLAG_DUPLICATE (1u<<0) /* exact same element exists, print other */
#define FLAG_EXPAND (1u<<1) /* element with same name exists, expand */
#define FLAG_REFERENCE (1u<<2)
#define FLAG_NOMIN (1u<<3)
#define FLAG_NOMAX (1u<<4)
struct cfgmeta {
char *name;
char *title;
char *pattern;
char *description;
unsigned int flags;
const char *type;
const char *unit;
const char *range;
const char **values;
};
struct cfgelem {
const char *name;
struct cfgelem *children;
struct cfgelem *attributes;
int multiplicity;
const char *value;
const char *description;
struct cfgmeta meta;
};
int makedescription(
struct cfgelem *elem,
const struct cfgunit *units,
const char *(*xlat)(const char *, const char **));
int makepattern(
struct cfgelem *elem,
const struct cfgunit *units);
const char *schema(void);
const char *url(void);
const char *name(const struct cfgelem *elem);
int ismoved(const struct cfgelem *elem);
int isdeprecated(const struct cfgelem *elem);
int isgroup(const struct cfgelem *elem);
int isnop(const struct cfgelem *elem);
int isbool(const struct cfgelem *elem);
int isint(const struct cfgelem *elem);
int isstring(const struct cfgelem *elem);
int isenum(const struct cfgelem *elem);
int islist(const struct cfgelem *elem);
int minimum(const struct cfgelem *elem);
int maximum(const struct cfgelem *elem);
int haschildren(const struct cfgelem *elem);
int hasattributes(const struct cfgelem *elem);
struct cfgelem *firstelem(const struct cfgelem *list);
struct cfgelem *nextelem(const struct cfgelem *list, const struct cfgelem *elem);
const struct cfgunit *findunit(const struct cfgunit *units, const char *name);
void print(FILE *out, unsigned int cols, const char *fmt, ...);
int printrnc(FILE *out, struct cfgelem *elem, const struct cfgunit *units);
int printxsd(FILE *out, struct cfgelem *elem, const struct cfgunit *units);
int printmd(FILE *out, struct cfgelem *elem, const struct cfgunit *units);

290
src/tools/ddsconf/md.c Normal file
View file

@ -0,0 +1,290 @@
/*
* Copyright(c) 2020 ADLINK Technology Limited and others
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License
* v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
*/
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include "ddsconf.h"
#include "dds/ddsrt/heap.h"
static const char *xlatmd(const char *str, const char **end)
{
static struct { const char *search; const char *replace; } tr[] = {
{ "<p>", "" }, { "</p>", "" },
{ "<b>", "" }, { "</b>", "" },
{ "<i>", "" }, { "</i>", "" },
{ "<li>", " * " }, { "</li>", "\n" },
{ "<ul>", "" }, { "</ul>", "" },
{ "<sup>", "^" }, { "</sup>", "" },
{ "*", "\\*" }, { "_", "\\_" },
{ NULL, NULL }
};
/* replace </p>\s*<p> by double newline */
if (strncmp(str, "</p>", 4) == 0) {
const char *ptr = str + 4;
while (*ptr == '\n' || *ptr == ' ') ptr++;
if (strncmp(ptr, "<p>", 3) == 0) {
*end = ptr;
return "\n\n";
}
}
for (size_t cnt = 0; tr[cnt].search; cnt++) {
size_t len = strlen(tr[cnt].search);
if (strncmp(str, tr[cnt].search, len) == 0) {
*end = str + len;
return tr[cnt].replace;
}
}
return NULL;
}
static char hashes[16];
static void printhead(
FILE *out,
unsigned int level,
unsigned int flags,
struct cfgelem *elem,
const struct cfgunit *units)
{
(void)level;
(void)flags;
(void)units;
assert(level < sizeof(hashes));
hashes[level+1] = '\0';
fprintf(out, "%s %s\n", hashes, elem->meta.title);
hashes[level+1] = '#';
}
static void printlink(
FILE *out,
unsigned int level,
unsigned int flags,
struct cfgelem *elem,
const struct cfgunit *units)
{
int chr;
(void)level;
(void)flags;
(void)units;
assert(elem->meta.title);
fputc('#', out);
for (const char *ptr = elem->meta.title; *ptr; ptr++) {
chr = (unsigned char)*ptr;
if (chr >= 'A' && chr <= 'Z')
chr = (chr - 'A') + 'a';
if (chr >= 'a' && chr <= 'z')
fputc(chr, out);
}
}
static void printtype(
FILE *out,
unsigned int level,
unsigned int flags,
struct cfgelem *elem,
const struct cfgunit *units)
{
(void)level;
(void)flags;
(void)units;
assert(!isgroup(elem));
if (isbool(elem)) {
fprintf(out, "Boolean\n");
} else if (islist(elem)) {
assert(elem->meta.values);
fprintf(out, "One of:\n");
if (elem->value && strlen(elem->value))
fprintf(out, "* Keyword: %s\n", elem->value);
fprintf(out, "* Comma-separated list of: ");
for (const char **v = elem->meta.values; *v; v++) {
fprintf(out, "%s%s", v == elem->meta.values ? "" : ", ", *v);
}
fprintf(out, "\n");
if (!elem->value || !strlen(elem->value))
fprintf(out, "* Or empty\n");
} else if (isenum(elem)) {
assert(elem->meta.values);
fprintf(out, "One of: ");
for (const char **v = elem->meta.values; *v; v++) {
fprintf(out, "%s%s", v == elem->meta.values ? "" : ", ", *v);
}
fprintf(out, "\n");
} else if (isint(elem)) {
fprintf(out, "Integer\n");
} else if (elem->meta.unit) {
fprintf(out, "Number-with-unit\n");
} else if (isstring(elem)) {
fprintf(out, "Text\n");
}
}
#define FLAG_LF (1u<<0)
static void printattr(
FILE *out,
unsigned int level,
unsigned int flags,
struct cfgelem *elem,
const struct cfgunit *units)
{
if (flags & FLAG_LF)
fputs("\n\n\n", out);
printhead(out, level, flags, elem, units);
printtype(out, level, flags, elem, units);
fputs("\n", out);
if (elem->description)
fputs(elem->meta.description, out);
}
static void printelem(
FILE *out,
unsigned int level,
unsigned int flags,
struct cfgelem *elem,
const struct cfgunit *units)
{
if (flags & FLAG_LF)
fprintf(out, "\n\n\n");
printhead(out, level, flags, elem, units);
flags &= ~FLAG_LF;
if (hasattributes(elem)) {
int cnt = 0;
const char *sep = "Attributes: ";
struct cfgelem *ce = firstelem(elem->attributes);
while (ce) {
if (!isnop(ce)) {
fprintf(out, "%s[%s](", sep, name(ce));
printlink(out, level, flags, ce, units);
fprintf(out, ")");
sep = ", ";
cnt++;
}
ce = nextelem(elem->attributes, ce);
}
if (cnt != 0) {
fprintf(out, "\n");
flags |= FLAG_LF;
}
}
if (haschildren(elem)) {
const char *sep = "Children: ";
struct cfgelem *ce = firstelem(elem->children);
while (ce) {
fprintf(out, "%s[%s](", sep, name(ce));
printlink(out, level, flags, ce, units);
fprintf(out, ")");
sep = ", ";
ce = nextelem(elem->children, ce);
}
fprintf(out, "\n");
flags |= FLAG_LF;
} else if (!isgroup(elem)) {
if (flags & FLAG_LF)
fprintf(out, "\n");
printtype(out, level+1, flags, elem, units);
flags |= FLAG_LF;
}
if (elem->description) {
if (flags & FLAG_LF)
fprintf(out, "\n");
fputs(elem->meta.description, out);
}
if (hasattributes(elem)) {
struct cfgelem *ce = firstelem(elem->attributes);
while (ce) {
if (!isnop(ce))
printattr(out, level, flags, ce, units);
ce = nextelem(elem->attributes, ce);
}
}
if (isgroup(elem)) {
struct cfgelem *ce = firstelem(elem->children);
while (ce) {
printelem(out, level+1, flags, ce, units);
ce = nextelem(elem->children, ce);
}
}
}
static int maketitles(
struct cfgelem *elem, int attr, const char *path, size_t pathlen)
{
char *str;
size_t namelen = 0, len = 0;
struct cfgelem *ce;
if (ismoved(elem) || isdeprecated(elem) || elem->meta.title)
return 0;
namelen = strlen(name(elem));
if (!(str = ddsrt_malloc(pathlen + namelen + (attr ? 4 : 2))))
return -1;
memcpy(str, path, pathlen);
len += pathlen;
if (attr) {
str[len++] = '[';
str[len++] = '@';
} else {
str[len++] = '/';
}
memcpy(str+len, name(elem), namelen);
len += namelen;
if (attr) {
str[len++] = ']';
}
str[len] = '\0';
elem->meta.title = str;
ce = firstelem(elem->children);
while (ce) {
if (maketitles(ce, 0, str, len) == -1)
return -1;
ce = nextelem(elem->children, ce);
}
ce = firstelem(elem->attributes);
while (ce) {
if (maketitles(ce, 1, str, len) == -1)
return -1;
ce = nextelem(elem->attributes, ce);
}
return 0;
}
static int initmd(struct cfgelem *elem, const struct cfgunit *units)
{
if (ismoved(elem) || isdeprecated(elem))
return 0;
if (makedescription(elem, units, xlatmd) == -1)
return -1;
for (struct cfgelem *ce = elem->children; ce && ce->name; ce++) {
if (initmd(ce, units) == -1)
return -1;
}
for (struct cfgelem *ce = elem->attributes; ce && ce->name; ce++) {
if (initmd(ce, units) == - 1)
return -1;
}
return 0;
}
int printmd(FILE *out, struct cfgelem *elem, const struct cfgunit *units)
{
if (initmd(elem, units) == -1)
return -1;
if (maketitles(elem, 0, "/", 1) == -1)
return -1;
memset(hashes, '#', sizeof(hashes));
printelem(out, 0u, 0u, elem, units);
return 0;
}

163
src/tools/ddsconf/rnc.c Normal file
View file

@ -0,0 +1,163 @@
/*
* Copyright(c) 2020 ADLINK Technology Limited and others
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License
* v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
*/
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "ddsconf.h"
#include "dds/ddsrt/heap.h"
#define FLAG_AMP (1u<<0)
#define FLAG_ROOT (1u<<1)
static const char *amp[] = { "", "& " };
static const char docfmt[] = "%s[ a:documentation [ xml:lang=\"en\" \"\"\"\n";
static const char elemfmt[] = "%selement %s {\n";
static const char attrfmt[] = "%sattribute %s {\n";
static const char *suffix(const struct cfgelem *elem)
{
if (minimum(elem) == 0)
return maximum(elem) == 1 ? "?" : "*";
else
return maximum(elem) == 1 ? "" : "+";
}
static void
printtype(
FILE *out,
unsigned int cols,
unsigned int flags,
const struct cfgelem *elem,
const struct cfgunit *units)
{
(void)flags;
(void)units;
if (strcmp(elem->meta.type, "string") == 0) {
if (elem->meta.unit != NULL) {
print(out, cols, "%s%s\n", amp[(flags & FLAG_AMP)], elem->meta.unit);
} else {
print(out, cols, "%stext\n", amp[(flags & FLAG_AMP)]);
}
} else if (strcmp(elem->meta.type, "bool") == 0) {
print(out, cols, "%sxsd:boolean\n", amp[(flags & FLAG_AMP)]);
} else if (strcmp(elem->meta.type, "int") == 0) {
print(out, cols, "%sxsd:integer\n", amp[(flags & FLAG_AMP)]);
} else if (strcmp(elem->meta.type, "enum") == 0) {
assert(elem->meta.pattern != NULL);
print(out, cols, "%s%s\n", amp[(flags & FLAG_AMP)], elem->meta.pattern);
} else if (strcmp(elem->meta.type, "list") == 0) {
assert(elem->meta.pattern != NULL);
print(out, cols, "%sxsd:token { pattern = \"%s\" }\n", amp[(flags & FLAG_AMP)], elem->meta.pattern);
} else {
print(out, cols, "%sempty\n", amp[(flags & FLAG_AMP)]);
}
}
static void
printattr(
FILE *out,
unsigned int cols,
unsigned int flags,
const struct cfgelem *elem,
const struct cfgunit *units)
{
assert(!ismoved(elem) && !isdeprecated(elem));
if (elem->description != NULL) {
print(out, cols, docfmt, amp[(flags & FLAG_AMP)]);
fputs(elem->meta.description, out);
print(out, 0, "\"\"\" ] ]\n");
flags &= ~FLAG_AMP;
}
print(out, cols, attrfmt, amp[(flags & FLAG_AMP)], name(elem));
printtype(out, cols+2, flags, elem, units);
print(out, cols, "}%s\n", suffix(elem));
}
static void printelem(
FILE *out,
unsigned int cols,
unsigned int flags,
struct cfgelem *elem,
const struct cfgunit *units)
{
struct cfgelem *ce;
assert(!ismoved(elem) && !isdeprecated(elem));
if (elem->description != NULL) {
print(out, cols, docfmt, amp[(flags & FLAG_AMP)]);
fputs(elem->meta.description, out);
print(out, 0, "\"\"\" ] ]\n");
flags &= ~FLAG_AMP;
}
print(out, cols, elemfmt, amp[(flags & FLAG_AMP)], name(elem));
flags &= ~FLAG_AMP;
ce = firstelem(elem->attributes);
while (ce) {
if (!isnop(ce)) {
printattr(out, cols+2, flags & ~FLAG_ROOT, ce, units);
flags |= FLAG_AMP;
}
ce = nextelem(elem->attributes, ce);
}
if (haschildren(elem)) {
ce = firstelem(elem->children);
while (ce) {
printelem(out, cols+2, flags & ~FLAG_ROOT, ce, units);
flags |= FLAG_AMP;
ce = nextelem(elem->children, ce);
}
} else if (!hasattributes(elem) ||
!(isgroup(elem) || (isstring(elem) && !elem->meta.unit)))
{
printtype(out, cols+2, flags, elem, units);
}
print(out, cols, "}%s\n", (flags & FLAG_ROOT) ? "" : suffix(elem));
}
static int initrnc(struct cfgelem *elem, const struct cfgunit *units)
{
if (ismoved(elem) || isdeprecated(elem))
return 0;
if (makedescription(elem, units, 0) == -1)
return -1;
if (makepattern(elem, units) == -1)
return -1;
for (struct cfgelem *ce = elem->children; ce && ce->name; ce++) {
if (initrnc(ce, units) == -1)
return -1;
}
for (struct cfgelem *ce = elem->attributes; ce && ce->name; ce++) {
if (initrnc(ce, units) == -1)
return -1;
}
return 0;
}
int printrnc(FILE *out, struct cfgelem *elem, const struct cfgunit *units)
{
if (initrnc(elem, units) == -1)
return -1;
print(out, 0, "default namespace = \"%s\"\n", url());
print(out, 0, "namespace a = \"http://relaxng.org/ns/compatibility/annotations/1.0\"\n");
print(out, 0, "grammar {\n");
print(out, 0, " start =\n");
printelem(out, 2, FLAG_ROOT, elem, units);
for(const struct cfgunit *cu = units; cu->name; cu++) {
static const char *fmt = " %s = xsd:token { pattern = \"%s\" }\n";
print(out, 0, fmt, cu->name, cu->pattern);
}
print(out, 0, "}");
return 0;
}

421
src/tools/ddsconf/xsd.c Normal file
View file

@ -0,0 +1,421 @@
/*
* Copyright(c) 2020 ADLINK Technology Limited and others
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License
* v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
*/
#include <assert.h>
#include <limits.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "ddsconf.h"
static const char *xlatxsd(const char *str, const char **end)
{
const char *sub;
if (*str == '&') /* ampersand */
sub = "&amp;";
else if (*str == '<') /* less-than sign */
sub = "&lt;";
else if (*str == '>') /* greater-than sign */
sub ="&gt;";
else
return NULL;
*end = ++str;
return sub;
}
static const char *isbuiltintype(const struct cfgelem *elem)
{
if (strcmp(elem->meta.type, "bool") == 0) {
return "boolean";
} else if (strcmp(elem->meta.type, "int") == 0) {
return "integer";
} else if (strcmp(elem->meta.type, "string") == 0) {
return "string";
}
return NULL;
}
static int iscomplextype(const struct cfgelem *elem)
{
return haschildren(elem) != 0 || hasattributes(elem) != 0 || isgroup(elem);
}
static void
printdesc(
FILE *out,
unsigned int cols,
unsigned int flags,
const struct cfgelem *elem,
const struct cfgunit *units)
{
(void)flags;
(void)units;
if (!elem->description)
return;
assert(elem->meta.description);
print(out, cols+0, "<xs:annotation>\n");
print(out, cols+2, "<xs:documentation>\n");
fputs(elem->meta.description, out);
fputs("</xs:documentation>\n", out);
print(out, cols+0, "</xs:annotation>\n");
}
static void
printenum(
FILE *out,
unsigned int cols,
unsigned int flags,
const struct cfgelem *elem,
const struct cfgunit *units)
{
(void)flags;
(void)units;
print(out, cols+0, "<xs:simpleType>\n");
print(out, cols+2, "<xs:restriction base=\"xs:token\">\n");
for(const char **v = elem->meta.values; v && *v; v++) {
print(out, cols+4, "<xs:enumeration value=\"%s\"/>\n", *v);
}
print(out, cols+2, "</xs:restriction>\n");
print(out, cols+0, "</xs:simpleType>\n");
}
static void
printlist(
FILE *out,
unsigned int cols,
unsigned int flags,
const struct cfgelem *elem,
const struct cfgunit *units)
{
(void)flags;
(void)units;
print(out, cols+0, "<xs:simpleType>\n");
print(out, cols+2, "<xs:restriction base=\"xs:token\">\n");
print(out, cols+4, "<xs:pattern value=\"%s\"/>\n", elem->meta.pattern);
print(out, cols+2, "</xs:restriction>\n");
print(out, cols+0, "</xs:simpleType>\n");
}
static void
printattr(
FILE *out,
unsigned int cols,
unsigned int flags,
const struct cfgelem *elem,
const struct cfgunit *units)
{
const char fmt[] = "<xs:attribute name=\"%s\"%s>\n";
char type[64], required[32];
(void)flags;
(void)units;
assert(elem != NULL);
assert(!haschildren(elem));
if (ismoved(elem) || isdeprecated(elem) || isnop(elem))
return;
type[0] = '\0';
if (elem->meta.unit)
snprintf(type, sizeof(type), " type=\"config:%s\"", elem->meta.unit);
else if (!isstring(elem))
snprintf(type, sizeof(type), " type=\"xs:%s\"", isbuiltintype(elem));
required[0] = '\0';
if (minimum(elem))
snprintf(type, sizeof(type), " use=\"required\"");
print(out, cols, fmt, name(elem), type, required);
printdesc(out, cols+2, flags, elem, units);
print(out, cols, "</xs:attribute>\n");
}
static void printelem(
FILE *out,
unsigned int cols,
unsigned int flags,
const struct cfgelem *elem,
const struct cfgunit *units);
static void
printref(
FILE *out,
unsigned int cols,
unsigned int flags,
const struct cfgelem *elem,
const struct cfgunit *units)
{
if (elem->meta.flags & FLAG_EXPAND) {
printelem(out, cols, flags | FLAG_REFERENCE, elem, units);
} else {
char minattr[32] = "";
char maxattr[32] = "";
const char fmt[] = "<xs:element %s%sref=\"%s:%s\"/>\n";
if (!(flags & FLAG_NOMIN) && minimum(elem) != 1)
snprintf(minattr, sizeof(minattr), "minOccurs=\"%d\" ", minimum(elem));
if (!(flags & FLAG_NOMAX) &&maximum(elem) == 0)
snprintf(maxattr, sizeof(maxattr), "maxOccurs=\"unbounded\" ");
else if (!(FLAG_NOMAX) && maximum(elem) != 1)
snprintf(maxattr, sizeof(maxattr), "maxOccurs=\"%d\" ", maximum(elem));
print(out, cols, fmt, minattr, maxattr, schema(), name(elem));
}
}
static void
printcomplextype(
FILE *out,
unsigned int cols,
unsigned int flags,
const struct cfgelem *elem,
const struct cfgunit *units)
{
char minattr[32] = "";
char maxattr[32] = "";
assert(!ismoved(elem) && !isdeprecated(elem));
if (flags & FLAG_REFERENCE) {
if (minimum(elem) != 1)
snprintf(minattr, sizeof(minattr), "minOccurs=\"%d\" ", minimum(elem));
if (maximum(elem) == 0)
snprintf(maxattr, sizeof(maxattr), "maxOccurs=\"unbounded\" ");
else if (maximum(elem) != 1)
snprintf(maxattr, sizeof(maxattr), "maxOccurs=\"%d\" ", maximum(elem));
}
print(out, cols, "<xs:element %s%sname=\"%s\">\n", minattr, maxattr, name(elem));
printdesc(out, cols+2, flags, elem, units);
flags &= ~(FLAG_NOMIN | FLAG_NOMAX);
if (!haschildren(elem) && !hasattributes(elem)) {
/* special case, group has only deprecated children and/or attributes */
print(out, cols+2, "<xs:complexType/>\n");
} else {
int cnt;
unsigned int ofst = 0;
print(out, cols+2, "<xs:complexType>\n");
if ((cnt = haschildren(elem))) {
const char *cont = NULL;
struct cfgelem *ce;
int min[2], max[2], eq[2] = { 1, 1 };
assert(isgroup(elem));
minattr[0] = '\0';
maxattr[0] = '\0';
if (cnt == 1) {
/* minOccurs and maxOccurs placed in element */
cont = "sequence";
} else {
assert(cnt > 1);
ce = firstelem(elem->children);
min[0] = min[1] = minimum(ce);
max[0] = max[1] = maximum(ce);
assert(min[1] <= max[1] || max[1] == 0);
ce = nextelem(elem->children, ce);
assert(ce);
while (ce) {
min[1] = minimum(ce);
max[1] = maximum(ce);
assert(min[1] <= max[1] || max[1] == 0);
if (min[1] != min[0]) {
eq[0] = 0;
if (min[1] < min[0])
min[0] = min[1];
}
if (max[1] != max[0]) {
eq[1] = 0;
if ((max[0] != 0 && max[1] > max[0]) || max[1] == 0)
max[0] = max[1];
}
ce = nextelem(elem->children, ce);
}
if (min[0] > 1 || max[0] != 1 /* unbounded or >1 */) {
cont = "choice";
if (eq[0]) {
if (min[0] != 1)
snprintf(minattr, sizeof(minattr), " minOccurs=\"%d\"", min[0]);
flags |= FLAG_NOMIN;
}
if (eq[1]) {
if (max[0] == 0)
snprintf(maxattr, sizeof(maxattr), " maxOccurs=\"unbounded\"");
else if (max[0] != 1)
snprintf(maxattr, sizeof(maxattr), " maxOccurs=\"%d\"", max[0]);
flags |= FLAG_NOMAX;
}
} else {
/* any order, each zero or one time */
/* minOccurs placed in element maxOccurs always one */
cont = "all";
}
}
print(out, cols+4, "<xs:%s%s%s>\n", cont, minattr, maxattr);
ce = firstelem(elem->children);
while (ce) {
printref(out, cols+6, flags, ce, units);
ce = nextelem(elem->children, ce);
}
print(out, cols+4, "</xs:%s>\n", cont);
flags &= ~(FLAG_NOMIN | FLAG_NOMAX);
} else if (!isgroup(elem) && (!isstring(elem) || elem->meta.unit)) {
ofst = 4;
print(out, cols+4, "<xs:simpleContent>\n");
if (isenum(elem) || islist(elem)) {
print(out, cols+6, "<xs:restriction base=\"xs:anyType\">\n");
if (isenum(elem))
printenum(out, cols+8, flags, elem, units);
else
printlist(out, cols+8, flags, elem, units);
} else {
const char extfmt[] = "<xs:extension base=\"%s:%s\">\n";
if (elem->meta.unit)
print(out, cols+6, extfmt, schema(), elem->meta.unit);
else
print(out, cols+6, extfmt, "xs", isbuiltintype(elem));
}
}
if (hasattributes(elem)) {
struct cfgelem *ce;
ce = firstelem(elem->attributes);
while (ce) {
printattr(out, cols+ofst+4, flags, ce, units);
ce = nextelem(elem->attributes, ce);
}
}
if (!isgroup(elem) && (!isstring(elem) || elem->meta.unit)) {
if (isenum(elem) || islist(elem))
print(out, cols+6, "</xs:restriction>\n");
else
print(out, cols+6, "</xs:extension>\n");
print(out, cols+4, "</xs:simpleContent>\n");
}
print(out, cols+2, "</xs:complexType>\n");
}
print(out, cols, "</xs:element>\n");
}
static void
printsimpletype(
FILE *out,
unsigned int cols,
unsigned int flags,
const struct cfgelem *elem,
const struct cfgunit *units)
{
char min[32] = "";
char max[32] = "";
const char *type;
const char fmt[] = "<xs:element %s%sname=\"%s\">\n";
const char builtinfmt[] = "<xs:element %s%sname=\"%s\" type=\"%s:%s\">\n";
assert(!ismoved(elem) && !isdeprecated(elem));
if (flags & FLAG_REFERENCE) {
if (minimum(elem) != 1)
snprintf(min, sizeof(min), "minOccurs=\"%d\" ", minimum(elem));
if (maximum(elem) == 0)
snprintf(max, sizeof(max), "maxOccurs=\"unbounded\" ");
else if (maximum(elem) != 1)
snprintf(max, sizeof(max), "maxOccurs=\"%d\" ", maximum(elem));
}
if (!(type = isbuiltintype(elem)))
print(out, cols, fmt, min, max, name(elem));
else if (elem->meta.unit)
print(out, cols, builtinfmt, min, max, name(elem), schema(), elem->meta.unit);
else
print(out, cols, builtinfmt, min, max, name(elem), "xs", type);
printdesc(out, cols+2, flags, elem, units);
if (isenum(elem))
printenum(out, cols+2, flags, elem, units);
else if (islist(elem))
printlist(out, cols+2, flags, elem, units);
print(out, cols, "</xs:element>\n");
}
static void
printelem(
FILE *out,
unsigned int cols,
unsigned int flags,
const struct cfgelem *elem,
const struct cfgunit *units)
{
struct cfgelem *ce;
assert(!(ismoved(elem) || isdeprecated(elem)));
if (!(elem->meta.flags & FLAG_DUPLICATE) && (flags & FLAG_EXPAND)) {
if (iscomplextype(elem))
printcomplextype(out, cols, flags, elem, units);
else
printsimpletype(out, cols, flags, elem, units);
}
if (!(flags & FLAG_REFERENCE)) {
ce = firstelem(elem->children);
while (ce) {
if (ce->meta.flags & FLAG_EXPAND)
flags &= ~FLAG_EXPAND;
else
flags |= FLAG_EXPAND;
printelem(out, cols, flags, ce, units);
ce = nextelem(elem->children, ce);
}
}
}
static int initxsd(struct cfgelem *elem, const struct cfgunit *units)
{
if (ismoved(elem) || isdeprecated(elem))
return 0;
if (makedescription(elem, units, &xlatxsd) == -1)
return -1;
if (makepattern(elem, units) == -1)
return -1;
for (struct cfgelem *ce = elem->children; ce && ce->name; ce++) {
if (initxsd(ce, units) == -1)
return -1;
}
for (struct cfgelem *ce = elem->attributes; ce && ce->name; ce++) {
if (initxsd(ce, units) == -1)
return -1;
}
return 0;
}
int printxsd(FILE *out, struct cfgelem *elem, const struct cfgunit *units)
{
if (initxsd(elem, units) == -1)
return -1;
print(out, 0, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
print(out, 0, "<xs:schema xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" "
"elementFormDefault=\"qualified\" targetNamespace=\"%s\" xmlns:%s=\"%s\">\n",
url(), schema(), url());
printelem(out, 2, FLAG_EXPAND, elem, units);
for (const struct cfgunit *cu = units; cu->name; cu++) {
print(out, 2, "<xs:simpleType name=\"%s\">\n", cu->name);
print(out, 4, "<xs:restriction base=\"xs:token\">\n");
print(out, 6, "<xs:pattern value=\"%s\"/>\n", cu->pattern);
print(out, 4, "</xs:restriction>\n");
print(out, 2, "</xs:simpleType>\n");
}
print(out, 0, "</xs:schema>");
return 0;
}