1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
|
VOTPARSE -- VOTable parser API. This interface allows a VOTable to read
read or created from a number of calling languages. XML parsing is done
using a SAX parser on input, when creating new VOTables the API is used
to populate the VOTable structure before writing the final output.
We attempt to implement all aspects of the VOTable v1.2 specification and
remain compatible with earlier versions to the extent possible.
Public Interface:
-----------------
vot = vot_openVOTABLE (str|fname)
vot_closeVOTABLE (vot)
res = vot_getRESOURCE (vot|res)
tab = vot_getTABLE (res)
field = vot_getFIELD (tab)
data = vot_getDATA (tab)
tdata = vot_getTABLEDATA (data) // data elements
tr = vot_getTR (tdata)
td = vot_getTD (tr)
bin = vot_getBINARY (data)
fits = vot_getFITS (data)
group = vot_getGROUP (vot|res|tab|group)
fldref = vot_getFIELDRef (group)
parref = vot_getPARAMRef (group)
desc = vot_getDESCRIPTION (handle)
param = vot_getPARAM (handle)
info = vot_getINFO (handle)
stream = vot_getSTREAM (bin|fits)
val = vot_getVALUES (field|param|info)
min = vot_getMIN (val)
max = vot_getMAX (val)
opt = vot_getOPTION (val)
link = vot_getLINK (res|info|param|field|table)
sys = vot_getCOOSYS (vot) // backwards compat.
handle = vot_newNode (parent, type)
vot_attachNode (parent, handle)
vot_deleteNode (handle)
Output Routines:
vot_writeVOTable (vot, fd)
vot_writeHTML (vot, fd)
vot_writeDelimitedTable (vot, fd, delim) w
Convenience Functions:
str = vot_getTableCell (tdata, row, col)
n = vot_getNRows (tdata)
n = vot_getNCols (tdata)
type = vot_getDATAType (data)
str = vot_getAttr (handle, attr)
stat = vot_setAttr (handle, attr, value)
str = vot_getValue (handle)
str = vot_setValue (handle, value)
len = vot_getLength (handle)
len = vot_getNumberOf (handle, type)
handle = vot_findByAttr (parent, type, attr, value)
handle[] = vot_findInGroup (group, TY_FIELD|TY_PARAM)
Low-level Interface (Private?):
handle = vot_getNext (handle)
handle = vot_getSibling (handle)
handle = vot_getChild (handle)
handle = vot_getChildOfType (handle, type)
handle = vot_getParent (handle)
type = vot_valueOf (handle)
------------------------------------------------------------------------------
Data Structures
---------------
#define TY_VOTABLE 1
#define TY_RESOURCE 2
#define TY_FIELD 3
#define TY_PARAM 4
#define TY_INFO 5
#define TY_ROW 6
#define TY_DATA 7
#define TY_VALUES 8
#define TY_STREAM 9
#define TY_LINK 10
#define TY_FIELDREF 11
#define TY_PARAMREF 12
#define TY_OPTION 13
#define TY_MIN 14
#define TY_MAX 15
#define TY_FITS 16
#define TY_TABLE 17
#define TY_GROUP 18
#define TY_COOSYS 19
#define MAX_ATTRS 11
#define SZ_ATTR_NAME 16
#define SZ_ATTR_VALUE 64
typedef unsigned int handle_t;
typedef struct {
char name[SZ_ATTR_NAME]; /* attribute name */
char value[SZ_ATTR_VALUE]; /* attribute value */
} Attr, *AttrP;
typedef struct {
char type; /* node type */
void *next; /* sibling node */
void *child; /* child nodes */
void *child_last; /* last child node */
void *parent; /* parent node */
char *data; /* value string */
Attr attr[MAX_ATTRS]; /* attribute array */
int nattrs; /* number of attributes */
} Node, *NodeP;
- Need to keep track of last child so we can easily append new tags
- The 'data' string is any text in the xml element, e.g. given
<TD>yada yada yada</TD>
the 'data' string is then 'yada yada yada'
- structs should be calloc'd to initialize to NULL
------------------------------------------------------------------------------
Notes:
------
- The 'handle' is an opaque reference to the node that is meant to
be language-neutral. In reality it is just the pointer to the Node
struct cast as an integer. Note that on 64-bit platforms, bindings
will require the handle to be typed as a 'long'value.
- The vot_openVOTable() is used both for reading and writing VOTables.
The read, the argument may a string which is a filename to be read or
a literal string assumed to be the VOTable document itself. If
passed a NULL pointer, a new document structure will be created and
we assume we'll write it out later.
- The various vot_get<Tag>() functions in reality only return the single
handle of the specified type, or a NULL if not found. This node
is required to be a child of the argument (parent) node.
vot_getFIELD (handle_t parent)
{
Node *ip = (handle_t) NULL;:
Node *p = (Node *) parent;
Node *c = p->child;
int ptype = p->type;
if (ptype != TY_TABLE) // check for valid parent
fprintf (stderr, "Invalid node for FIELDs\n");
for (ip=c; ip; ip = ip->next)
if (ip->type == TY_FIELD)
break;
return ((handle_t) ip);
}
- A call to vot_getNext() returns the next sibling of the same type as
the arg. In contrast, vot_getSibling() returns the next sibling node
regardless of type.
vot_getNext (handle_t tag)
{
Node *ip:
Node *np = (Node *) tag;
int type = p->type;
for (ip=np->next; ip; ip = ip->next)
if (ip->type == np->type)
return ((handle_t) ip);
return ((handle_t) NULL);
}
vot_getSibling (handle_t tag)
{
Node *np = (Node *) tag;
return ((handle_t) np->next);
}
- vot_newNode() creates an empty node structure, other calls are used
to populate the attributes, values, or child nodes. OTOH, methods
like vot_attachNode() / vot_deleteNode() use handles that can refer
to sub-trees. This makes it easy to refer to e.g. an entire RESOURCE
and then attach it to a new document or delete entirely. The
methods that care of adusting the child/next pointers.
vot_newNode (handle_t parent, int type)
{
Node *np = (Node *) calloc (1, sizeof (Node));
np->type = type;
return ((handle_t) np);;
}
vot_attachNode (handle_t parent, handle_t node)
{
Node *p = (Node *) parent,
*n = (Node *) node,
*last = p->child_last;
if (p->child)
last->next = n; // append existing children
else
p->child = n; // make an only child
p->child_last = n // update parent
}
vot_deleteNode (handle_t node)
{
Node *n = (Node *) node,
*p = n->parent,
*last = p->child_last,
*prev = (Node *) NULL;
if (p->child == n) { // node is first child
p->child = n->next;
} else {
// Find the previous sibling node
for (prev=p->child; prev->next != n; prev=prev->next)
;
prev->next = n->next;
if (p->child_last == n) //update parent
p->child_last = prev;
}
vot_freeNode (n); // free the node sub-tree
}
The vot_freeNode() method would need to walk the tree in a
depth-first manner to free the Node strucutres. The vot_closeVOTable()
then is simply a call to this on the root node
- Fortran bindings should use the subroutine model to avoid having to
declare numerous functions. E.g.
The C method
field = vot_getFIELD (tab)
is used in Fortran as
call getFIELD (tab, field)
Implementation of the wrapper is then simply
void getFIELD (handle_t parent, handle_t *field)
{
*field = vot_getFIELD (parent);
}
Methods that pass strings need to take into account the hidden length
parameter used in fortran. E.g.
The C method
valueStr = vot_getAttr (handle, attr)
is used in Fortran as
call getAttr (handle, attr, valueStr)
However, the implementation is
void getAttr (handle_t parent, char *a, char *v, int alen, int vlen)
{
*v = vot_getAttr (parent, a);
}
The 'alen' and 'vlen' args are added by the Fortran compiler to pass
in the length of the string. In some cases we may also want to pass
in the max length of a result string so we don't overflow a buffer,
or else return the length of the string we found. Since Fortran is
call-by-address in either of these cases the length becomes a pointer
declaration.
------------------------------------------------------------------------------
PseudoCode:
-----------
1) Read a VOTable, processing each RESOURCE in the file. Note we don't
handle *nested* RESOURCEs here.
vot = vot_openVOTable (fname)
// loop over RESOURCES
res = vot_getRESOURCE (vot, i);
printf ("Table has toplevel %d RESOURCE elements\n",
vot_getLength (res));
while ( res ) {
tab = vot_getTABLE (res)
// Print column info
for (field=vot_getFIELD(tab); field; field=vot_getNext(field)) {
strcpy (col[i].name, vot_getAttr (field, "name")
strcpy (col[i].ucd, vot_getAttr (field, "ucd")
}
// Get the data element
data = vot_getDATA (tab)
switch (vot_getDataType (data))
case TY_TABLEDATA:
// Get data stored as a TABLEDATA xml block
tdata = vot_getTABLEDATA (data)
if (use_direct) {
// Get the table data cells by direct index
tr = vot_getTR(tdata)
nrows = vot_getLength (tr)
ncols = vot_getLength (vot_getTD(tr))
for (l=0; l < nrows ; l++) {
for (m=0; m < ncols; m++) {
str = vot_getTableCell (tdata, l, m)
} else {
// Get the table data cells by looping over rows/cols
for (tr=vot_getTR (tdata); tr; tr=vot_getNext(tr))
for (td=vot_getTD(tr); td; td=vot_getNext(td))
str = vot_getValue (td);
}
break
case TY_BINARY:
// Get data stored as inline binary. If the encoding of
// the stream is base64 read the sequence of bytes and decode.
bin = vot_getBINARY (data)
stream = vot_getSTREAM (bin)
if (strcmp ("base64", vot_getAttr(stream, "encoding") == 0)
str = vot_getValue (stream)
break
case TY_FITS:
// Read FITS data. Assumes a particular extension of an
// MEF is available for download at the given href.
fits = vot_getFITS (data)
extnum = vot_getAttr (fits, "extnum") // get extension no.
stream = vot_getSTREAM (fits)
href = vot_getAttr (stream, "href")
....download the FITS file ....
break
default:
error (0, "Invalid table DATA type.")
}
res = vot_getNext(res) // get next resource
}
vot_closeVOTable (vot)
2) Print all the PARAM elements in a table with a single RESOURCE
a) Use the low-level interface dealing with document structure
res = vot_getRESOURCE (vot)
for (p = vot_getChild (res); p; p = vot_getSibling (p)) {
if (vot_typeOf (p) == TY_PARAM)
printf ("PARAM name=%s value=%s\n",
vot_getAttr(p, "name"), vot_getAttr(p, "value"))
}
b) Use the common hi-level interface
res = vot_getRESOURCE (vot)
for (p = vot_getPARAM (res); p; p = vot_getNext (p)) {
printf ("PARAM name=%s value=%s\n",
vot_getAttr(p, "name"), vot_getAttr(p, "value"))
}
3) Check a VOTable to see if it is an error return
a) SCS-only (Preferred) Method - INFO as a child of VOTABLE
vot = vot_openVOTable (fname)
if ((info = vot_getINFO (vot)) {
if (strcsecmp (vot_getAttr (info, "name"), "error") == 0)
return ((errMsg = vot_getAttr (info, "value")))
} else
return ("file is okay")
b) Alternate Method - PARAM as a child of RESOURCE "allowed" for SCS,
required for other DAL services. For SCS, the name/id are different,
later DAL services use 'QUERY_STATUS'.
vot = vot_openVOTable (fname)
res = vot_getRESOURCE (vot)
param = vot_getPARAM (res)
info = vot_getINFO (res)
if (strcasecmp(vot_getAttr (param, "name"), "error") == 0) {
// SCS alternate method where PARAM defines value the error string
return (vot_getAttr (param, "value"))
} else if (strcasecmp(vot_getAttr(info,"name"),"QUERY_STATUS") == 0) {
// All-other DAL methods where and INFO of the RESOURCE defines
// a QUERY_STATUS of the result.
if ( ((val = vot_getAttr (info, "value")), "OK") == 0)
return (NULL); // no error
else
return (vot_getValue (info)) // return error message
}
NOTES: We should make this an interface convenience. A real
error will be a minimal VOTable error return we can parse, but
DAL2 services like TAP may experience overflow where we don't see
an error result until a max-records/timeout is reached and the
error INFO is at the end of the output. In this last case the
error INFO isn't seen until after table data. To be real, we
need to then search all INFO children of the RESOURCE rather than
just the first one shown in the above.
4) Create a new VOTable from computed values
vot = vot_openVOTable (NULL) // initialize
res = vot_newNode (vot, TY_RESOURCE) // create empty resource
vot_setAttr (res, "id", "newTable") // set table name
desc = vot_newNode (vot, TY_DESCRIPTION) // set description
vot_setValue (desc, "This is a test description")
tab = vot_newNode (res, TY_TABLE) // create a TABLE
for (i=0; i < 10; i++) {
f = vot_newNode (tab, TY_FIELD)
sprintf (colname, "col%d", i)
vot_setAttr (f, "name", colname)
vot_setAttr (f, "id", colname)
: :
}
data = vot_newNode (tab, TY_DATA) // create a DATA
tdata = vot_newNode (data, TY_TABLEDATA) // create a TABLEDATA
for (i=0; i < nrows; i++) {
tr = vot_newNode (tdata, TY_TR) // create a row
for (j=0; j < ncols; j++) {
td = vot_newNode (tr, TY_TD) // create a col
vot_setValue (td, (char *)data[i,j])
}
}
info = vot_newNode (tab, TY_INFO) // create a DATA
vot_setAttr (info, "id", "STATUS")
vot_setAttr (info, "value", "OK")
vot_writeVOTable (vot, stdout) // write it out
4-A) Create a new VOTable from computed values (Alternate Method)
vot = vot_openVOTable (NULL) // initialize
res = vot_newRESOURCE (vot) // create empty resource
vot_setAttr (res, "id", "newTable") // set table name
// set description
desc = vot_newDESCRIPTION (vot, "This is a test description")
tab = vot_newTABLE (res) // create a TABLE
for (i=0; i < 10; i++) {
f = vot_newFIELD (tab)
sprintf (colname, "col%d", i)
vot_setAttr (f, "name", colname)
vot_setAttr (f, "id", colname)
: :
}
data = vot_newDATA (tab) // create a DATA
tdata = vot_newTABLEDATA (data) // create a TABLEDATA
for (i=0; i < nrows; i++) {
tr = vot_newTR (tdata) // create a row
for (j=0; j < ncols; j++) {
td = vot_newTD (tr) // create a col
vot_setValue (td, (char *)data[i,j])
}
}
info = vot_newINFO (tab) // create a DATA
vot_setAttr (info, "id", "STATUS")
vot_setAttr (info, "value", "OK")
vot_writeVOTable (vot, stdout) // write it out
5) Concatenate the RESOURCEs from two input table to a new output table
vot1 = vot_openVOTABLE ("file1.xml") // open input tables
vot2 = vot_openVOTABLE ("file2.xml")
res1 = vot_getRESOURCE (vot1) // get resources
res2 = vot_getRESOURCE (vot2)
vot3 = vot_openVOTABLE (NULL) // open output table
vot_attachNode (vot3, vot1) // add resources
vot_attachNode (vot3, vot2)
vot_writeVOTable (vot2, stdout) // write it out
vot_closeVOTABLE (vot1) // clean up
vot_closeVOTABLE (vot2)
vot_closeVOTABLE (vot3)
6) Extract all the values in the columns specified by a GROUP
vot = vot_openVOTable (fname)
res = vot_getRESOURCE (vot)
tab = vot_getTABLE (res)
// Look for the GROUP by name
for (group=vot_getGROUP(res); group; group=vot_getNext(group))
if (strcasecmp (vot_getAttr(group,"name"), "CoolStuff") == 0)
break;
// Gather the column numbers for the FIELDs in the GROUP
for (fref=vot_getFIELDRef(group); fref; fref=vot_getNext(fref)) {
// Get the field references
ref = vot_getAttr (fref, "ref")
// Find the FIELD with the referenced ID attribute,
field = vot_findByAttr (tab, TY_FIELD, "ID", ref)
// Convert to a column number to extract
fp = vot_getField (tab) // start FIELD
colnum = -1
while (fp && fp != field) {
colnum++; // 0-indexed list
fp = vot_getNext (fp)
}
cols[numInGroup++] = colnum
}
// Print out the data in selected columns
data = vot_getDATA (tab)
tdata = vot_getTABLEDATA (data)
tr = vot_getTR(tdata)
nrows = vot_getLength (tr)
for (i=0; i < nrows ; i++) {
for (j=0; j < numInGroup; j++)
printf ("%s ", vot_getTableCell (tdata, i, cols[j])
printf ("\n")
}
|