/*************************************************************************
 * Copyright (c) 2011 AT&T Intellectual Property 
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors: Details at https://graphviz.org
 *************************************************************************/

#include <cgraph/agxbuf.h>
#include <cgraph/gv_ctype.h>
#include <xdot/xdot.h>
#include <stdlib.h>
#include <string.h>
#include <util/alloc.h>
#include <util/prisize_t.h>
#include <util/unreachable.h>

/* the parse functions should return NULL on error */
static char *parseReal(char *s, double *fp)
{
    char *p;
    double d;

    d = strtod(s, &p);
    if (p == s) return 0;
	
    *fp = d;
    return (p);
}


static char *parseInt(char *s, int *ip)
{
    char* endp;

    *ip = (int)strtol (s, &endp, 10);
    if (s == endp)
	return 0;
    else
	return endp;
}

static char *parseUInt(char *s, unsigned int *ip)
{
    char* endp;

    *ip = (unsigned int)strtoul (s, &endp, 10);
    if (s == endp)
	return 0;
    else
	return endp;
}

static char *parseRect(char *s, xdot_rect * rp)
{
    char* endp;

    rp->x = strtod (s, &endp);
    if (s == endp)
	return 0;
    else
	s = endp;

    rp->y = strtod (s, &endp);
    if (s == endp)
	return 0;
    else
	s = endp;

    rp->w = strtod (s, &endp);
    if (s == endp)
	return 0;
    else
	s = endp;

    rp->h = strtod (s, &endp);
    if (s == endp)
	return 0;
    else
	s = endp;

    return s;
}

static char *parsePolyline(char *s, xdot_polyline * pp)
{
    unsigned i;
    xdot_point *pts;
    xdot_point *ps;
    char* endp;

    s = parseUInt(s, &i);
    if (!s) return NULL;
    pts = ps = gv_calloc(i, sizeof(ps[0]));
    pp->cnt = i;
    for (i = 0; i < pp->cnt; i++) {
	ps->x = strtod (s, &endp);
	if (s == endp) {
	    free (pts);
	    return NULL;
	}
	else
	    s = endp;
	ps->y = strtod (s, &endp);
	if (s == endp) {
	    free (pts);
	    return NULL;
	}
	else
	    s = endp;
	ps->z = 0;
	ps++;
    }
    pp->pts = pts;
    return s;
}

static char *parseString(char *s, char **sp)
{
    int i;
    s = parseInt(s, &i);
    if (!s || i <= 0) return 0;
    while (*s && *s != '-') s++;
    if (*s) s++;
    else {
	return 0;
    }

    // The leading number we just read indicates the count of originating
    // characters in the upcoming string. But the string may contain \-escaped
    // characters. So the count alone does not tell us how many bytes we need to
    // now read.
    agxbuf c = {0};
    int j = 0;
    for (int accounted = 0; accounted < i; ++j) {
	if (s[j] == '\0') {
	    agxbfree(&c);
	    return 0;
	}
	agxbputc(&c, s[j]);
	// only count this character if it was not an escape prefix
	if (s[j] != '\\' || (j > 0 && s[j - 1] == '\\')) {
	    ++accounted;
	}
    }

    *sp = agxbdisown(&c);
    return s + j;
}

static char *parseAlign(char *s, xdot_align * ap)
{
    int i;
    s = parseInt(s, &i);

    if (i < 0)
	*ap = xd_left;
    else if (i > 0)
	*ap = xd_right;
    else
	*ap = xd_center;
    return s;
}

#define CHK(s) if(!s){*error=1;return 0;}

static char *parseOp(xdot_op * op, char *s, drawfunc_t ops[], int* error)
{
    char* cs;
    xdot_color clr;

    *error = 0;
    while (gv_isspace(*s))
	s++;
    switch (*s++) {
    case 'E':
	op->kind = xd_filled_ellipse;
	s = parseRect(s, &op->u.ellipse);
	CHK(s);
	if (ops)
	    op->drawfunc = ops[xop_ellipse];
	break;

    case 'e':
	op->kind = xd_unfilled_ellipse;
	s = parseRect(s, &op->u.ellipse);
	CHK(s);
	if (ops)
	    op->drawfunc = ops[xop_ellipse];
	break;

    case 'P':
	op->kind = xd_filled_polygon;
	s = parsePolyline(s, &op->u.polygon);
	CHK(s);
	if (ops)
	    op->drawfunc = ops[xop_polygon];
	break;

    case 'p':
	op->kind = xd_unfilled_polygon;
	s = parsePolyline(s, &op->u.polygon);
	CHK(s);
	if (ops)
	    op->drawfunc = ops[xop_polygon];
	break;

    case 'b':
	op->kind = xd_filled_bezier;
	s = parsePolyline(s, &op->u.bezier);
	CHK(s);
	if (ops)
	    op->drawfunc = ops[xop_bezier];
	break;

    case 'B':
	op->kind = xd_unfilled_bezier;
	s = parsePolyline(s, &op->u.bezier);
	CHK(s);
	if (ops)
	    op->drawfunc = ops[xop_bezier];
	break;

    case 'c':
	s = parseString(s, &cs);
	CHK(s);
	cs = parseXDotColor (cs, &clr);
	CHK(cs);
	if (clr.type == xd_none) {
	    op->kind = xd_pen_color;
	    op->u.color = clr.u.clr;
	    if (ops)
		op->drawfunc = ops[xop_pen_color];
	}
	else {
	    op->kind = xd_grad_pen_color;
	    op->u.grad_color = clr;
	    if (ops)
		op->drawfunc = ops[xop_grad_color];
	}
	break;

    case 'C':
	s = parseString(s, &cs);
	CHK(s);
	cs = parseXDotColor (cs, &clr);
	CHK(cs);
	if (clr.type == xd_none) {
	    op->kind = xd_fill_color;
	    op->u.color = clr.u.clr;
	    if (ops)
		op->drawfunc = ops[xop_fill_color];
	}
	else {
	    op->kind = xd_grad_fill_color;
	    op->u.grad_color = clr;
	    if (ops)
		op->drawfunc = ops[xop_grad_color];
	}
	break;

    case 'L':
	op->kind = xd_polyline;
	s = parsePolyline(s, &op->u.polyline);
	CHK(s);
	if (ops)
	    op->drawfunc = ops[xop_polyline];
	break;

    case 'T':
	op->kind = xd_text;
	s = parseReal(s, &op->u.text.x);
	CHK(s);
	s = parseReal(s, &op->u.text.y);
	CHK(s);
	s = parseAlign(s, &op->u.text.align);
	CHK(s);
	s = parseReal(s, &op->u.text.width);
	CHK(s);
	s = parseString(s, &op->u.text.text);
	CHK(s);
	if (ops)
	    op->drawfunc = ops[xop_text];
	break;

    case 'F':
	op->kind = xd_font;
	s = parseReal(s, &op->u.font.size);
	CHK(s);
	s = parseString(s, &op->u.font.name);
	CHK(s);
	if (ops)
	    op->drawfunc = ops[xop_font];
	break;

    case 'S':
	op->kind = xd_style;
	s = parseString(s, &op->u.style);
	CHK(s);
	if (ops)
	    op->drawfunc = ops[xop_style];
	break;

    case 'I':
	op->kind = xd_image;
	s = parseRect(s, &op->u.image.pos);
	CHK(s);
	s = parseString(s, &op->u.image.name);
	CHK(s);
	if (ops)
	    op->drawfunc = ops[xop_image];
	break;

    case 't':
	op->kind = xd_fontchar;
	s = parseUInt(s, &op->u.fontchar);
	CHK(s);
	if (ops)
	    op->drawfunc = ops[xop_fontchar];
	break;


    case '\0':
	s = 0;
	break;

    default:
	*error = 1;
	s = 0;
	break;
    }
    return s;
}

#define XDBSIZE 100

/* parseXDotFOn:
 * Parse and append additional xops onto a given xdot object.
 * Return x.
 */ 
xdot *parseXDotFOn(char *s, drawfunc_t fns[], size_t sz, xdot *x) {
    xdot_op op;
    char *ops;
    size_t oldsz, bufsz;
    int error;

    if (!s)
	return x;

    if (!x) {
	x = gv_alloc(sizeof(*x));
	if (sz <= sizeof(xdot_op))
	    sz = sizeof(xdot_op);

	/* cnt, freefunc, ops, flags zeroed by gv_alloc */
	x->sz = sz;
    }
    size_t initcnt = x->cnt;
    sz = x->sz;

    if (initcnt == 0) {
	bufsz = XDBSIZE;
	ops = gv_calloc(XDBSIZE, sz);
    }
    else {
	bufsz = initcnt + XDBSIZE;
	ops = gv_recalloc(x->ops, initcnt, bufsz, sz);
    }

    while ((s = parseOp(&op, s, fns, &error))) {
	if (x->cnt == bufsz) {
	    oldsz = bufsz;
	    bufsz *= 2;
	    ops = gv_recalloc(ops, oldsz, bufsz, sz);
	}
	*(xdot_op *) (ops + x->cnt * sz) = op;
	x->cnt++;
    }
    if (error)
	x->flags |= XDOT_PARSE_ERROR;
    if (x->cnt) {
	x->ops = gv_recalloc(ops, bufsz, x->cnt, sz);
    }
    else {
	free (ops);
	free (x);
	x = NULL;
    }

    return x;

}

xdot *parseXDotF(char *s, drawfunc_t fns[], size_t sz) {
    return parseXDotFOn (s, fns, sz, NULL);
}

xdot *parseXDot(char *s)
{
    return parseXDotF(s, 0, 0);
}

typedef int (*pf)(void*, char*, ...);

static void printRect(xdot_rect * r, pf print, void *info)
{
    agxbuf buf = {0};

    agxbprint(&buf, " %.02f", r->x);
    agxbuf_trim_zeros(&buf);
    print(info, "%s", agxbuse(&buf));
    agxbprint(&buf, " %.02f", r->y);
    agxbuf_trim_zeros(&buf);
    print(info, "%s", agxbuse(&buf));
    agxbprint(&buf, " %.02f", r->w);
    agxbuf_trim_zeros(&buf);
    print(info, "%s", agxbuse(&buf));
    agxbprint(&buf, " %.02f", r->h);
    agxbuf_trim_zeros(&buf);
    print(info, "%s", agxbuse(&buf));
    agxbfree(&buf);
}

static void printPolyline(xdot_polyline * p, pf print, void *info)
{
    agxbuf buf = {0};

    print(info, " %" PRISIZE_T, p->cnt);
    for (size_t i = 0; i < p->cnt; i++) {
	agxbprint(&buf, " %.02f", p->pts[i].x);
	agxbuf_trim_zeros(&buf);
	print(info, "%s", agxbuse(&buf));
	agxbprint(&buf, " %.02f", p->pts[i].y);
	agxbuf_trim_zeros(&buf);
	print(info, "%s", agxbuse(&buf));
    }
    agxbfree(&buf);
}

static void printString(char *p, pf print, void *info)
{
    print(info, " %" PRISIZE_T " -%s", strlen(p), p);
}

static void printFloat(double f, pf print, void *info, int space) {
    agxbuf buf = {0};

    if (space)
	agxbprint(&buf, " %.02f", f);
    else
	agxbprint(&buf, "%.02f", f);
    agxbuf_trim_zeros(&buf);
    print(info, "%s", agxbuse(&buf));
    agxbfree(&buf);
}

static void printAlign(xdot_align a, pf print, void *info)
{
    switch (a) {
    case xd_left:
	print(info, " -1");
	break;
    case xd_right:
	print(info, " 1");
	break;
    case xd_center:
	print(info, " 0");
	break;
    default:
	UNREACHABLE();
    }
}

static void
toGradString (agxbuf* xb, xdot_color* cp)
{
    int i, n_stops;
    xdot_color_stop* stops;

    if (cp->type == xd_linear) {
	agxbputc (xb, '[');
	printFloat (cp->u.ling.x0, (pf)agxbprint, xb, 0);
	printFloat (cp->u.ling.y0, (pf)agxbprint, xb, 1);
	printFloat (cp->u.ling.x1, (pf)agxbprint, xb, 1);
	printFloat (cp->u.ling.y1, (pf)agxbprint, xb, 1);
	n_stops = cp->u.ling.n_stops;
	stops = cp->u.ling.stops;
    }
    else {
	agxbputc (xb, '(');
	printFloat (cp->u.ring.x0, (pf)agxbprint, xb, 0);
	printFloat (cp->u.ring.y0, (pf)agxbprint, xb, 1);
	printFloat (cp->u.ring.r0, (pf)agxbprint, xb, 1);
	printFloat (cp->u.ring.x1, (pf)agxbprint, xb, 1);
	printFloat (cp->u.ring.y1, (pf)agxbprint, xb, 1);
	printFloat (cp->u.ring.r1, (pf)agxbprint, xb, 1);
	n_stops = cp->u.ring.n_stops;
	stops = cp->u.ring.stops;
    }
    agxbprint(xb, " %d", n_stops);
    for (i = 0; i < n_stops; i++) {
	printFloat (stops[i].frac, (pf)agxbprint, xb, 1);
	printString (stops[i].color, (pf)agxbprint, xb);
    }

    if (cp->type == xd_linear)
	agxbputc (xb, ']');
    else
	agxbputc (xb, ')');
}

typedef void (*print_op)(xdot_op * op, pf print, void *info, int more);

static void printXDot_Op(xdot_op * op, pf print, void *info, int more)
{
    agxbuf xb = {0};
    switch (op->kind) {
    case xd_filled_ellipse:
	print(info, "E");
	printRect(&op->u.ellipse, print, info);
	break;
    case xd_unfilled_ellipse:
	print(info, "e");
	printRect(&op->u.ellipse, print, info);
	break;
    case xd_filled_polygon:
	print(info, "P");
	printPolyline(&op->u.polygon, print, info);
	break;
    case xd_unfilled_polygon:
	print(info, "p");
	printPolyline(&op->u.polygon, print, info);
	break;
    case xd_filled_bezier:
	print(info, "b");
	printPolyline(&op->u.bezier, print, info);
	break;
    case xd_unfilled_bezier:
	print(info, "B");
	printPolyline(&op->u.bezier, print, info);
	break;
    case xd_pen_color:
	print(info, "c");
	printString(op->u.color, print, info);
	break;
    case xd_grad_pen_color:
	print(info, "c");
	toGradString (&xb, &op->u.grad_color);
	printString(agxbuse(&xb), print, info);
	break;
    case xd_fill_color:
	print(info, "C");
	printString(op->u.color, print, info);
	break;
    case xd_grad_fill_color:
	print(info, "C");
	toGradString (&xb, &op->u.grad_color);
	printString(agxbuse(&xb), print, info);
	break;
    case xd_polyline:
	print(info, "L");
	printPolyline(&op->u.polyline, print, info);
	break;
    case xd_text:
	print(info, "T %.f %.f", op->u.text.x, op->u.text.y);
	printAlign(op->u.text.align, print, info);
	print(info, " %.f", op->u.text.width);
	printString(op->u.text.text, print, info);
	break;
    case xd_font:
	print(info, "F");
	printFloat(op->u.font.size, print, info, 1);
	printString(op->u.font.name, print, info);
	break;
    case xd_fontchar:
	print(info, "t %u", op->u.fontchar);
	break;
    case xd_style:
	print(info, "S");
	printString(op->u.style, print, info);
	break;
    case xd_image:
	print(info, "I");
	printRect(&op->u.image.pos, print, info);
	printString(op->u.image.name, print, info);
	break;
    default: // invalid type; ignore
	break;
    }
    if (more)
	print(info, " ");
    agxbfree (&xb);
}

static void jsonRect(xdot_rect * r, pf print, void *info)
{
  print(info, "[%.06f,%.06f,%.06f,%.06f]", r->x, r->y, r->w, r->h);
}

static void jsonPolyline(xdot_polyline * p, pf print, void *info)
{
    print(info, "[");
    for (size_t i = 0; i < p->cnt; i++) {
	print(info, "%.06f,%.06f", p->pts[i].x, p->pts[i].y);
	if (i < p->cnt-1) print(info, ",");
    }
    print(info, "]");
}

static void jsonString(char *p, pf print, void *info)
{
    char c;

    print(info, "\"");
    while ((c = *p++)) {
	if (c == '"') print(info, "\\\"");
	else if (c == '\\') print(info, "\\\\");
	else print(info, "%c", c);
    }
    print(info, "\"");
}

static void jsonXDot_Op(xdot_op * op, pf print, void *info, int more)
{
    agxbuf xb = {0};
    switch (op->kind) {
    case xd_filled_ellipse:
	print(info, "{\"E\" : ");
	jsonRect(&op->u.ellipse, print, info);
	break;
    case xd_unfilled_ellipse:
	print(info, "{\"e\" : ");
	jsonRect(&op->u.ellipse, print, info);
	break;
    case xd_filled_polygon:
	print(info, "{\"P\" : ");
	jsonPolyline(&op->u.polygon, print, info);
	break;
    case xd_unfilled_polygon:
	print(info, "{\"p\" : ");
	jsonPolyline(&op->u.polygon, print, info);
	break;
    case xd_filled_bezier:
	print(info, "{\"b\" : ");
	jsonPolyline(&op->u.bezier, print, info);
	break;
    case xd_unfilled_bezier:
	print(info, "{\"B\" : ");
	jsonPolyline(&op->u.bezier, print, info);
	break;
    case xd_pen_color:
	print(info, "{\"c\" : ");
	jsonString(op->u.color, print, info);
	break;
    case xd_grad_pen_color:
	print(info, "{\"c\" : ");
	toGradString (&xb, &op->u.grad_color);
	jsonString(agxbuse(&xb), print, info);
	break;
    case xd_fill_color:
	print(info, "{\"C\" : ");
	jsonString(op->u.color, print, info);
	break;
    case xd_grad_fill_color:
	print(info, "{\"C\" : ");
	toGradString (&xb, &op->u.grad_color);
	jsonString(agxbuse(&xb), print, info);
	break;
    case xd_polyline:
	print(info, "{\"L\" :");
	jsonPolyline(&op->u.polyline, print, info);
	break;
    case xd_text:
	print(info, "{\"T\" : [ %.f, %.f,", op->u.text.x, op->u.text.y);
	printAlign(op->u.text.align, print, info);
	print(info, ", %.f,", op->u.text.width);
	jsonString(op->u.text.text, print, info);
	print(info, "]");
	break;
    case xd_font:
	print(info, "{\"F\" : [");
	op->kind = xd_font;
	printFloat(op->u.font.size, print, info, 1);
	print(info, ",");
	jsonString(op->u.font.name, print, info);
	print(info, "]");
	break;
    case xd_fontchar:
	print(info, "{\"t\" :  %u", op->u.fontchar);
	break;
    case xd_style:
	print(info, "{\"S\" : ");
	jsonString(op->u.style, print, info);
	break;
    case xd_image:
	print(info, "{\"I\" : [");
	jsonRect(&op->u.image.pos, print, info);
	print(info, ",");
	jsonString(op->u.image.name, print, info);
	print(info, "]");
	break;
    default: // invalid type; ignore
	break;
    }
    if (more)
	print(info, "},\n");
    else
	print(info, "}\n");
    agxbfree (&xb);
}

static void _printXDot(xdot * x, pf print, void *info, print_op ofn)
{
    xdot_op *op;
    char *base = (char *) (x->ops);
    for (size_t i = 0; i < x->cnt; i++) {
	op = (xdot_op *) (base + i * x->sz);
	ofn(op, print, info, i < x->cnt - 1);
    }
}

char *sprintXDot(xdot * x)
{
    agxbuf xb = {0};
    _printXDot(x, (pf)agxbprint, &xb, printXDot_Op);
    return agxbdisown(&xb);
}

void fprintXDot(FILE * fp, xdot * x)
{
    _printXDot(x, (pf)fprintf, fp, printXDot_Op);
}

void jsonXDot(FILE * fp, xdot * x)
{
    fputs ("[\n", fp);
    _printXDot(x, (pf)fprintf, fp, jsonXDot_Op);
    fputs ("]\n", fp);
}

static void freeXOpData(xdot_op * x)
{
    switch (x->kind) {
    case xd_filled_polygon:
    case xd_unfilled_polygon:
	free(x->u.polyline.pts);
	break;
    case xd_filled_bezier:
    case xd_unfilled_bezier:
	free(x->u.polyline.pts);
	break;
    case xd_polyline:
	free(x->u.polyline.pts);
	break;
    case xd_text:
	free(x->u.text.text);
	break;
    case xd_fill_color:
    case xd_pen_color:
	free(x->u.color);
	break;
    case xd_grad_fill_color:
    case xd_grad_pen_color:
	freeXDotColor (&x->u.grad_color);
	break;
    case xd_font:
	free(x->u.font.name);
	break;
    case xd_style:
	free(x->u.style);
	break;
    case xd_image:
	free(x->u.image.name);
	break;
    default:
	break;
    }
}

void freeXDot (xdot * x)
{
    xdot_op *op;
    char *base;
    freefunc_t ff = x->freefunc;

    if (!x) return;
    base = (char *) (x->ops);
    for (size_t i = 0; i < x->cnt; i++) {
	op = (xdot_op *) (base + i * x->sz);
	if (ff) ff (op);
	freeXOpData(op);
    }
    free(base);
    free(x);
}

int statXDot (xdot* x, xdot_stats* sp)
{
    xdot_op *op;
    char *base;

    if (!x || !sp) return 1;
    memset(sp, 0, sizeof(xdot_stats));
    sp->cnt = x->cnt;
    base = (char *) (x->ops);
    for (size_t i = 0; i < x->cnt; i++) {
	op = (xdot_op *) (base + i * x->sz);
 	switch (op->kind) {
	case xd_filled_ellipse:
	case xd_unfilled_ellipse:
	    sp->n_ellipse++;
	    break;
	case xd_filled_polygon:
	case xd_unfilled_polygon:
	    sp->n_polygon++;
	    sp->n_polygon_pts += op->u.polygon.cnt;
	    break;
	case xd_filled_bezier:
	case xd_unfilled_bezier:
	    sp->n_bezier++;
	    sp->n_bezier_pts += op->u.bezier.cnt;
	    break;
	case xd_polyline:
	    sp->n_polyline++;
	    sp->n_polyline_pts += op->u.polyline.cnt;
	    break;
	case xd_text:
	    sp->n_text++;
	    break;
	case xd_image:
	    sp->n_image++;
	    break;
	case xd_fill_color:
	case xd_pen_color:
	    sp->n_color++;
	    break;
	case xd_grad_fill_color:
	case xd_grad_pen_color:
	    sp->n_gradcolor++;
	    break;
        case xd_font:
	    sp->n_font++;
	    break;
        case xd_fontchar:
	    sp->n_fontchar++;
	    break;
	case xd_style:
	    sp->n_style++;
	    break;
	default :
	    break;
	}
    }
    
    return 0;
}

#define CHK1(s) if(!s){free(stops);return NULL;}

/* radGradient:
 * Parse radial gradient spec
 * Return NULL on failure.
 */
static char*
radGradient (char* cp, xdot_color* clr)
{
    char* s = cp;
    int i;
    double d;
    xdot_color_stop* stops = NULL;

    clr->type = xd_radial;
    s = parseReal(s, &clr->u.ring.x0);
    CHK1(s);
    s = parseReal(s, &clr->u.ring.y0);
    CHK1(s);
    s = parseReal(s, &clr->u.ring.r0);
    CHK1(s);
    s = parseReal(s, &clr->u.ring.x1);
    CHK1(s);
    s = parseReal(s, &clr->u.ring.y1);
    CHK1(s);
    s = parseReal(s, &clr->u.ring.r1);
    CHK1(s);
    s = parseInt(s, &clr->u.ring.n_stops);
    CHK1(s);

    stops = gv_calloc(clr->u.ring.n_stops, sizeof(stops[0]));
    for (i = 0; i < clr->u.ring.n_stops; i++) {
	s = parseReal(s, &d);
	CHK1(s);
	stops[i].frac = d;
	s = parseString(s, &stops[i].color);
	CHK1(s);
    }
    clr->u.ring.stops = stops;

    return cp;
}

/* linGradient:
 * Parse linear gradient spec
 * Return NULL on failure.
 */
static char*
linGradient (char* cp, xdot_color* clr)
{
    char* s = cp;
    int i;
    double d;
    xdot_color_stop* stops = NULL;

    clr->type = xd_linear;
    s = parseReal(s, &clr->u.ling.x0);
    CHK1(s);
    s = parseReal(s, &clr->u.ling.y0);
    CHK1(s);
    s = parseReal(s, &clr->u.ling.x1);
    CHK1(s);
    s = parseReal(s, &clr->u.ling.y1);
    CHK1(s);
    s = parseInt(s, &clr->u.ling.n_stops);
    CHK1(s);

    stops = gv_calloc(clr->u.ling.n_stops, sizeof(stops[0]));
    for (i = 0; i < clr->u.ling.n_stops; i++) {
	s = parseReal(s, &d);
	CHK1(s);
	stops[i].frac = d;
	s = parseString(s, &stops[i].color);
	CHK1(s);
    }
    clr->u.ling.stops = stops;

    return cp;
}

/* parseXDotColor:
 * Parse xdot color spec: ordinary or gradient
 * The result is stored in clr.
 * Return NULL on failure.
 */
char*
parseXDotColor (char* cp, xdot_color* clr)
{
    char c = *cp;

    switch (c) {
    case '[' :
	return linGradient (cp+1, clr);
	break;
    case '(' :
	return radGradient (cp+1, clr);
	break;
    case '#' :
    case '/' :
	clr->type = xd_none; 
	clr->u.clr = cp;
	return cp;
	break;
    default :
	if (gv_isalnum(c)) {
	    clr->type = xd_none; 
	    clr->u.clr = cp;
	    return cp;
	}
	else
	    return NULL;
    }
}

void freeXDotColor (xdot_color* cp)
{
    int i;

    if (cp->type == xd_linear) {
	for (i = 0; i < cp->u.ling.n_stops; i++) {
	    free (cp->u.ling.stops[i].color);
	}
	free (cp->u.ling.stops);
    }
    else if (cp->type == xd_radial) {
	for (i = 0; i < cp->u.ring.n_stops; i++) {
	    free (cp->u.ring.stops[i].color);
	}
	free (cp->u.ring.stops);
    }
}

/**
 * @dir lib/xdot
 * @brief parsing and deparsing of xdot operations, API xdot.h
 *
 * [man 3 xdot](https://graphviz.org/pdf/xdot.3.pdf)
 *
 */
