XML Output: libxo - provide single API to output TXT, XML, JSON and HTML

Simon Gerraty sjg at juniper.net
Fri Jul 25 04:49:29 UTC 2014


Hi,

At a vendor summit a few years ago I asked about whether anyone but us
(Juniper) would be interested in the ablity to have standard BSD apps
output XML.

I was actually surprised by the amount of interest expressed.
I've occasionally nagged our UI team ever since for a clean and simple
API that we could contribute to address this.

We now have a what I think is a viable candidate
and we'd like to take the next steps towards contributing it and
converting at least a few apps.

Not only does it handle TXT and XML output but JSON and HTML as well,
and very rich HTML at that.
With some slick javascript - you can do amazing things with the level of
detail you can get out of this sort of thing.

The API is of necessity a bit more complex than just printf(3).
Considering the level of functionality available though it is a good
tradeoff.

The main open issue (assuming this functionality is still desired) is
support of wide charachters.

We figure the worst case solution is a sed(1) script to generate the wide
version of the API from the normal one, but perhaps simply always using
UTF8 would be a better solution?

Thanks
--sjg

The following from Phil provides some idea of the functionality
available and the API.
The one shown here uses the default output handle (stdout), but there
are variants that allow multiple output handles.

>Here's some sample output (from my libxo-ified "w"):
>
>(This uses an environment variable to trigger the mode and options:
> T X J and H are the modes (text, xml, json, html);
> P means pretty print (indent, newlines)
> I means print help (datatype, description), if provided (there aren't for "w")
> x means print xpath to the data)
>
>% foreach i ( T XP JP HP HPIx )
>    echo === $i ===
>    env LIBXO_OPTIONS=$i ./xtest -n | head -10
>    end
>=== T ===
> 6:47PM  up 18 days,  2:01, 9 user%s, load averages: 0.00, 0.00, 0.00
>USER             TTY      FROM              LOGIN@  IDLE WHAT
>phil             pts/0    76.182.32.73      5:09PM    33 /bin/sh
>phil             pts/1    76.182.32.73     05Jul14     2 /usr/bin/perl /u/phil/bin/plum (
>phil             pts/2    76.182.32.73     05Jul14     1 /bin/tcsh
>phil             pts/3    76.182.32.73     05Jul14 2days ssh dent
>phil             pts/4    76.182.32.73     Tue02PM 2days ssh svl-junos-d026.juniper.net
>phil             pts/5    76.182.32.73     Wed01AM 2days telnet man-o-war 2006
>phil             pts/6    76.182.32.73     Fri10PM 2days ssh 198.85.229.65
>phil             pts/7    76.182.32.73     Fri10PM 2days ssh zap
>=== XP ===
><uptime-information>
>  <time-of-day> 6:47PM</time-of-day>
>  <uptime seconds="1562436">18 days</uptime>
>  <uptime> 2:01</uptime>
>  <users>9</users>
>  <load-average-1>0.00</load-average-1>
>  <load-average-5>0.00</load-average-5>
>  <load-average-15>0.00</load-average-15>
>  <user-table>
>    <user-entry>
>=== JP ===
>"uptime-information": {
>  "time-of-day": " 6:47PM",
>  "uptime": "18 days",
>  "uptime":  2:01,
>  "users": 9,
>  "load-average-1": 0.00,
>  "load-average-5": 0.00,
>  "load-average-15": 0.00,
>  "user-table": {
>    "user-entry": [
>=== HP ===
><div class="line">
>  <div class="data" data-tag="time-of-day"> 6:47PM</div>
>  <div class="text"> </div>
>  <div class="text"> up</div>
>  <div class="text"> </div>
>  <div class="data" data-tag="uptime">18 days</div>
>  <div class="text">,</div>
>  <div class="text"> </div>
>  <div class="data" data-tag="uptime"> 2:01</div>
>  <div class="text">,</div>
>=== HPIx ===
><div class="line">
>  <div class="data" data-tag="time-of-day" data-xpath="/uptime-information/time-of-day"> 6:47PM</div>
>  <div class="text"> </div>
>  <div class="text"> up</div>
>  <div class="text"> </div>
>  <div class="data" data-tag="uptime" data-xpath="/uptime-information/uptime">18 days</div>
>  <div class="text">,</div>
>  <div class="text"> </div>
>  <div class="data" data-tag="uptime" data-xpath="/uptime-information/uptime"> 2:01</div>
>  <div class="text">,</div>
>
>Thanks,
> Phil
>
>-----
>
>FWIW: here's the diff for "w".  I don't have "wchar_t" support
>yet, so I just undid it for now.
>
>
>diff -rbu /usr/src/usr.bin/w/pr_time.c ./pr_time.c
>--- /usr/src/usr.bin/w/pr_time.c	2010-12-21 12:09:25.000000000 -0500
>+++ ./pr_time.c	2014-07-21 17:12:19.000000000 -0400
>@@ -55,10 +55,10 @@
> int
> pr_attime(time_t *started, time_t *now)
> {
>-	static wchar_t buf[256];
>+	static char buf[256];
> 	struct tm tp, tm;
> 	time_t diff;
>-	wchar_t *fmt;
>+	char *fmt;
> 	int len, width, offset = 0;
> 
> 	tp = *localtime(started);
>@@ -67,7 +67,7 @@
> 
> 	/* If more than a week, use day-month-year. */
> 	if (diff > 86400 * 7)
>-		fmt = L"%d%b%y";
>+		fmt = "%d%b%y";
> 
> 	/* If not today, use day-hour-am/pm. */
> 	else if (tm.tm_mday != tp.tm_mday ||
>@@ -75,23 +75,23 @@
> 		 tm.tm_year != tp.tm_year) {
> 	/* The line below does not take DST into consideration */
> 	/* else if (*now / 86400 != *started / 86400) { */
>-		fmt = use_ampm ? L"%a%I%p" : L"%a%H";
>+		fmt = use_ampm ? "%a%I%p" : "%a%H";
> 	}
> 
> 	/* Default is hh:mm{am,pm}. */
> 	else {
>-		fmt = use_ampm ? L"%l:%M%p" : L"%k:%M";
>+		fmt = use_ampm ? "%l:%M%p" : "%k:%M";
> 	}
> 
>-	(void)wcsftime(buf, sizeof(buf), fmt, &tp);
>-	len = wcslen(buf);
>-	width = wcswidth(buf, len);
>+	(void)strftime(buf, sizeof(buf), fmt, &tp);
>+	len = strlen(buf);
>+	width = len;
> 	if (len == width)
>-		(void)wprintf(L"%-7.7ls", buf);
>+		xo_emit("{:login-time/%-7.7s}", buf);
> 	else if (width < 7)
>-		(void)wprintf(L"%ls%.*s", buf, 7 - width, "      ");
>+	        xo_emit("{:login-time/%s}%.*s", buf, 7 - width, "      ");
> 	else {
>-		(void)wprintf(L"%ls", buf);
>+		xo_emit("{:login-time/%s}", buf);
> 		offset = width - 7;
> 	}
> 	return (offset);
>@@ -108,7 +108,7 @@
> 	/* If idle more than 36 hours, print as a number of days. */
> 	if (idle >= 36 * 3600) {
> 		int days = idle / 86400;
>-		(void)printf(" %dday%s ", days, days > 1 ? "s" : " " );
>+		xo_emit(" {:idle/%dday%s} ", days, days > 1 ? "s" : " " );
> 		if (days >= 100)
> 			return (2);
> 		if (days >= 10)
>@@ -117,15 +117,15 @@
> 
> 	/* If idle more than an hour, print as HH:MM. */
> 	else if (idle >= 3600)
>-		(void)printf(" %2d:%02d ",
>+		xo_emit(" {:idle/%2d:%02d/} ",
> 		    (int)(idle / 3600), (int)((idle % 3600) / 60));
> 
> 	else if (idle / 60 == 0)
>-		(void)printf("     - ");
>+		xo_emit("     - ");
> 
> 	/* Else print the minutes idle. */
> 	else
>-		(void)printf("    %2d ", (int)(idle / 60));
>+		xo_emit("    {:idle/%2d} ", (int)(idle / 60));
> 
> 	return (0); /* not idle longer than 9 days */
> }
>diff -rbu /usr/src/usr.bin/w/w.c ./w.c
>--- /usr/src/usr.bin/w/w.c	2010-12-21 12:09:25.000000000 -0500
>+++ ./w.c	2014-07-21 18:13:50.000000000 -0400
>@@ -86,6 +86,7 @@
> #include <unistd.h>
> #include <utmp.h>
> #include <vis.h>
>+#include <libxo/libxo.h>
> 
> #include "extern.h"
> 
>@@ -260,9 +261,12 @@
> 	}
> 	(void)fclose(ut);
> 
>+	xo_open_container("uptime-information");
>+
> 	if (header || wcmd == 0) {
> 		pr_header(&now, nusers);
> 		if (wcmd == 0) {
>+		        xo_close_container("uptime-information");
> 			(void)kvm_close(kd);
> 			exit(0);
> 		}
>@@ -274,11 +278,11 @@
> #define HEADER_WHAT		"WHAT\n"
> #define WUSED  (UT_NAMESIZE + UT_LINESIZE + W_DISPHOSTSIZE + \
> 		sizeof(HEADER_LOGIN_IDLE) + 3)	/* header width incl. spaces */ 
>-		(void)printf("%-*.*s %-*.*s %-*.*s  %s", 
>+		xo_emit("{T:/%-*.*s} {T:/%-*.*s} "
>+			"{T:/%-*.*s}  {T:LOGIN@}  {T:IDLE} {T:WHAT}\n",
> 				UT_NAMESIZE, UT_NAMESIZE, HEADER_USER,
> 				UT_LINESIZE, UT_LINESIZE, HEADER_TTY,
>-				W_DISPHOSTSIZE, W_DISPHOSTSIZE, HEADER_FROM,
>-				HEADER_LOGIN_IDLE HEADER_WHAT);
>+			W_DISPHOSTSIZE, W_DISPHOSTSIZE, HEADER_FROM);
> 	}
> 
> 	if ((kp = kvm_getprocs(kd, KERN_PROC_ALL, 0, &nentries)) == NULL)
>@@ -347,6 +351,9 @@
> 		}
> 	}
> 
>+	xo_open_container("user-table");
>+	xo_open_list("user-entry");
>+
> 	for (ep = ehead; ep != NULL; ep = ep->next) {
> 		char host_buf[UT_HOSTSIZE + 1];
> 		struct sockaddr_storage ss;
>@@ -356,6 +363,8 @@
> 		time_t t;
> 		int isaddr;
> 
>+		xo_open_instance("user-entry");
>+
> 		host_buf[UT_HOSTSIZE] = '\0';
> 		strncpy(host_buf, ep->utmp.ut_host, UT_HOSTSIZE);
> 		p = *host_buf ? host_buf : "-";
>@@ -388,6 +397,9 @@
> 			p = buf;
> 		}
> 		if (dflag) {
>+		        xo_open_container("process-table");
>+		        xo_open_list("process-entry");
>+
> 			for (dkp = ep->dkp; dkp != NULL; dkp = debugproc(dkp)) {
> 				const char *ptr;
> 
>@@ -395,23 +407,37 @@
> 				    dkp->ki_comm, MAXCOMLEN);
> 				if (ptr == NULL)
> 					ptr = "-";
>-				(void)printf("\t\t%-9d %s\n",
>+				xo_open_instance("process-entry");
>+				xo_emit("\t\t{:process-id/%-9d/%d} "
>+					"{:command/%s}\n",
> 				    dkp->ki_pid, ptr);
>+				xo_close_instance("process-entry");
> 			}
>+		        xo_close_list("process-entry");
>+		        xo_close_container("process-table");
> 		}
>-		(void)printf("%-*.*s %-*.*s %-*.*s ",
>+		xo_emit("{:user/%-*.*s/%@**@s} {:tty/%-*.*s/%@**@s} "
>+			"{:from/%-*.*s/%@**@s} ",
> 		    UT_NAMESIZE, UT_NAMESIZE, ep->utmp.ut_name,
> 		    UT_LINESIZE, UT_LINESIZE,
> 		    strncmp(ep->utmp.ut_line, "tty", 3) &&
> 		    strncmp(ep->utmp.ut_line, "cua", 3) ?
> 		    ep->utmp.ut_line : ep->utmp.ut_line + 3,
> 		    W_DISPHOSTSIZE, W_DISPHOSTSIZE, *p ? p : "-");
>+
> 		t = _time_to_time32(ep->utmp.ut_time);
> 		longattime = pr_attime(&t, &now);
> 		longidle = pr_idle(ep->idle);
>-		(void)printf("%.*s\n", argwidth - longidle - longattime,
>-		    ep->args);
>+		xo_emit("{:command/%.*s/%@*@s}\n",
>+			    argwidth - longidle - longattime, ep->args);
>+
>+		xo_close_instance("user-entry");
> 	}
>+
>+	xo_close_list("user-entry");
>+	xo_close_container("user-table");
>+	xo_close_container("uptime-information");
>+
> 	(void)kvm_close(kd);
> 	exit(0);
> }
>@@ -430,7 +456,7 @@
> 	 */
> 	if (strftime(buf, sizeof(buf),
> 	    use_ampm ? "%l:%M%p" : "%k:%M", localtime(nowp)) != 0)
>-		(void)printf("%s ", buf);
>+		xo_emit("{:time-of-day/%s} ", buf);
> 	/*
> 	 * Print how long system has been up.
> 	 */
>@@ -444,35 +470,45 @@
> 		uptime %= 3600;
> 		mins = uptime / 60;
> 		secs = uptime % 60;
>-		(void)printf(" up");
>+		xo_emit(" up");
>+		xo_attr("seconds", "%lu", (unsigned long) tp.tv_sec);
> 		if (days > 0)
>-			(void)printf(" %d day%s,", days, days > 1 ? "s" : "");
>+			xo_emit(" {:uptime/%d day%s},",
>+				days, days > 1 ? "s" : "");
> 		if (hrs > 0 && mins > 0)
>-			(void)printf(" %2d:%02d,", hrs, mins);
>+			xo_emit(" {:uptime/%2d:%02d},", hrs, mins);
> 		else if (hrs > 0)
>-			(void)printf(" %d hr%s,", hrs, hrs > 1 ? "s" : "");
>+			xo_emit(" {:uptime/%d hr%s},",
>+				hrs, hrs > 1 ? "s" : "");
> 		else if (mins > 0)
>-			(void)printf(" %d min%s,", mins, mins > 1 ? "s" : "");
>+			xo_emit(" {:uptime/%d min%s},",
>+				mins, mins > 1 ? "s" : "");
> 		else
>-			(void)printf(" %d sec%s,", secs, secs > 1 ? "s" : "");
>+			xo_emit(" {:uptime/%d sec%s},",
>+				secs, secs > 1 ? "s" : "");
> 	}
> 
> 	/* Print number of users logged in to system */
>-	(void)printf(" %d user%s", nusers, nusers == 1 ? "" : "s");
>+	xo_emit(" {:users/%d} user%s", nusers, nusers == 1 ? "" : "s");
> 
> 	/*
> 	 * Print 1, 5, and 15 minute load averages.
> 	 */
> 	if (getloadavg(avenrun, sizeof(avenrun) / sizeof(avenrun[0])) == -1)
>-		(void)printf(", no load average information available\n");
>+		xo_emit(", no load average information available\n");
> 	else {
>-		(void)printf(", load averages:");
>+	        static const char *format[] = {
>+		    " {:load-average-1/%.2f}",
>+		    " {:load-average-5/%.2f}",
>+		    " {:load-average-15/%.2f}",
>+		};
>+		xo_emit(", load averages:");
> 		for (i = 0; i < (int)(sizeof(avenrun) / sizeof(avenrun[0])); i++) {
> 			if (use_comma && i > 0)
>-				(void)printf(",");
>-			(void)printf(" %.2f", avenrun[i]);
>+				xo_emit(",");
>+			xo_emit(format[i], avenrun[i]);
> 		}
>-		(void)printf("\n");
>+		xo_emit("\n");
> 	}
> }
> 
>@@ -493,10 +529,9 @@
> usage(int wcmd)
> {
> 	if (wcmd)
>-		(void)fprintf(stderr,
>-		    "usage: w [-dhin] [-M core] [-N system] [user ...]\n");
>+		xo_error("usage: w [-dhin] [-M core] [-N system] [user ...]\n");
> 	else
>-		(void)fprintf(stderr, "usage: uptime\n");
>+		xo_error("usage: uptime\n");
> 	exit(1);
> }
> 
>


More information about the freebsd-arch mailing list