Passing an array from FORTRAN to C then passing it within C andReturning it to FORTRAN

D

deadpickle

Im trying to read an xml file. What happens is that I have a FORTRAN
program that calls a C library that uses libxml2 to read an xml file.
The passed array (from FORTRAN) needs to be passed to several
functions within the C program before being returned to the FORTRAN
program. My question is how do I pass the array within C and how do I
return the array to FORTRAN.

The code:
#include <stdio.h>
#include <libxml/parser.h>
#include <libxml/tree.h>
#include <libxml/xmlstring.h>
#include <libxml/xpath.h>

#ifdef LIBXML_TREE_ENABLED

static void get_item_values(xmlNode * b_node, int *max, double
ellipseinfo[5][*max], int mark) {

xmlNode *sub_node = NULL;
xmlChar *attrname;
int cluster = 0;

for (sub_node = b_node; sub_node; sub_node = sub_node->next) {
if (sub_node->type == XML_ELEMENT_NODE) {
if ( !xmlStrcmp(sub_node->name, "item") ) {
attrname = xmlGetNoNsProp(sub_node,"value");
double value = xmlXPathCastStringToNumber(attrname);
ellipseinfo[mark][cluster] = value;
cluster++;
}
}
}
}

static void print_element_names(xmlNode * a_node, double *ellipseinfo)
{
xmlNode *cur_node = NULL;
xmlChar *attrname;

for (cur_node = a_node; cur_node; cur_node = cur_node->next) {
if (cur_node->type == XML_ELEMENT_NODE) {
if ( !xmlStrcmp(cur_node->name, "datacolumn") ) {
attrname = xmlGetNoNsProp(cur_node, (const xmlChar *) "name");
if (xmlStrEqual(attrname, (const xmlChar *) "Latitude")) {
int mark = 0;
get_item_values(cur_node->children, max, ellipseinfo,mark);
}
else if (xmlStrEqual(attrname, (const xmlChar *) "Longitude")) {
int mark = 1;
get_item_values(cur_node->children, max, ellipseinfo[5]
[*max],mark);
}
if (xmlStrEqual(attrname, (const xmlChar *) "LatRadius")) {
int mark = 2;
get_item_values(cur_node->children, max, ellipseinfo[5]
[*max],mark);
}
if (xmlStrEqual(attrname, (const xmlChar *) "LonRadius")) {
int mark = 3;
get_item_values(cur_node->children, max, ellipseinfo[5]
[*max],mark);
}
if (xmlStrEqual(attrname, (const xmlChar *) "Orientation")) {
int mark = 4;
get_item_values(cur_node->children, max, ellipseinfo[5]
[*max],mark);
}
}
}
print_element_names(cur_node->children,max,ellipseinfo[5][*max]);
}
}

int
read_xml(int *max, char *filename, double ellipseinfo[5][*max])
{
xmlDoc *doc = NULL;
xmlNode *root_element = NULL;

LIBXML_TEST_VERSION

doc = xmlReadFile(filename, NULL, 0);

if (doc == NULL) {
printf("error: could not parse file %s\n", filename);
}

root_element = xmlDocGetRootElement(doc);

print_element_names(root_element, &ellipseinfo[5][max]);

xmlFreeDoc(doc);

xmlCleanupParser();

return;
}
#else
int main(void) {
fprintf(stderr, "Tree support not compiled in\n");
exit(1);
}
#endif
 
J

Jens Thoms Toerring

deadpickle said:
Im trying to read an xml file. What happens is that I have a FORTRAN
program that calls a C library that uses libxml2 to read an xml file.
The passed array (from FORTRAN) needs to be passed to several
functions within the C program before being returned to the FORTRAN
program. My question is how do I pass the array within C and how do I
return the array to FORTRAN.

First of all, in C you can't "pass" arrays, you only can pass
pointers to arrays. And the syntax for two-dimensional arrays
may obscur this a bit.

Arrays (be they one- or multi-dimensional) in C are stored
continously in memory, just as they are in FORTRAN. But the
important difference is the layout of the elements in me-
mory - in FORTRAN the elements of the first column come
first, then the elements of the second column etc. while
in C the elements of the first row are stored first, then
the elements of the second row etc. It looks as if you
are aware of that and thus some of the strange stuff you
are trying to do;-)

Lets start with the function which I guess you are trying to
call from FORTRAN:
int
read_xml(int *max, char *filename, double ellipseinfo[5][*max])

My guess is that under FORTRAN 'ellipseinfo' is a two-dimen-
sional array with 'max' columns and 5 elements per row. If
that assumption is wrong you can probably forget most of the
rest I am going to write.

Now this function signature may lead to trouble. At least
with C89 if you pass around two-dimensional arrays you have
to have a compile time constant at least for all the lower
dimension. Only the size of the highest dimension can be left
unspecified (but can't be a variable). Thus it would be ok to
have e.g.

double ellipseinfo[ ][ 5 ]

to tell the compiler it's a two-dimensional array with 5
elements per row and an unspecified number of columns but

double ellipseinfo[ 5 ][ *max ]

won't work with C89 since there were no variable length arrays.
I will start with describing how to do it in C89 and then add a
version that requires C99 (and which then is more similar to your
original version).
{
xmlDoc *doc = NULL;
xmlNode *root_element = NULL;

These initialization are ok but unnecessary.
LIBXML_TEST_VERSION

doc = xmlReadFile(filename, NULL, 0);

if (doc == NULL) {
printf("error: could not parse file %s\n", filename);

Shouldn't you return here? Also, error messages are typically
written to stderr instead of stdout, which is what printf()
is printing to. Thus it might be better to use
fprintf(stderr, "error: could not parse file %s\n", filename);
}

root_element = xmlDocGetRootElement(doc);

Shouldn't you also check if this function call succeeded?
print_element_names(root_element, &ellipseinfo[5][max]);

Now, assuming that the function signature had been ok this
would pass a pointer to the element with indices 5 and
'max' as the third argument to the function. Several pro-
blems: 'max' is a pointer so you would need at least '*max'
here as the second index. And the '&' in front of the array
is wrong. Finally, even under C89 you would have to pass
the value of '*max' first and then the array itself (without
any indices).
xmlFreeDoc(doc);

xmlCleanupParser();

return;
}

Now here's a version that may be better suited when you don't
have a C99 compiler. Note that the function accepts as the third
element a pointer to the array, we're going to treat this as a
flat array and deal with indexing later on manually.

void
read_xml( int * max,
char * filename,
double * ellipseinfo )
{
xmlDoc *doc;
xmlNode *root_element;

LIBXML_TEST_VERSION

doc = xmlReadFile( filename, NULL, 0 );

if ( doc == NULL )
{
fprintf( stderr, "error: could not parse file %s\n", filename );
return;
}

root_element = xmlDocGetRootElement( doc );

if ( root_element == NULL )
{
fprintf( stderr, "error: could not get root element\n" );
return;
}

print_element_names( root_element, ellipseinfo, *max );

xmlFreeDoc( doc );
xmlCleanupParser( );
}

Note that the call of print_element_names() now has an additional
argumenst, the maximum number of rows in the array. It is needed
to make sure we get the indexing right and to avoid writing past
the end of the array.The number of columns is well-known to be 5,
so we don't need to pass that also.

The print_element_names() function seemed too complicated to
me, so I changed it and added another function:

static void
print_element_names( xmlNode * node,
double * ellipseinfo,
int max_rows )
{
while ( node )
{
if ( node->type == XML_ELEMENT_NODE )
read_datacolumn( node, ellipseinfo, max_rows );
else
print_element_names( node->children, ellipseinfo, max_rows );

node = node->next;
}
}

As far as I understand it a node either has type XML_ELEMENT_NODE,
in which case you try to read in data for one of the types of data
columns, or it's a node that contains data columns as children, so
you recurse.

Let's continue with the function for reading a column;

static void
read_datacolumn( xmlNode * node,
double * ellipseinfo,
int max_rows )
{
xmlChar *attrname;
int index;
const xmlChar * keys[ ] = { ( const xmlChar * ) "Latitude",
( const xmlChar * ) "Longitude",
( const xmlChar * ) "LatRadius",
( const xmlChar * ) "LonRadius",
( const xmlChar * ) "Orientation" };

/* This should be a 'datacolumn' node with a 'name' attribute */

if ( ! xmlStrcmp( node->name, ( const xmlChar * ) "datacolumn" )
|| ! ( attrname = xmlGetNoNsProp( node, ( const xmlChar * ) "name" ) ) )
return;

/* The name must be one of those from the 'keys' array - if it is
we read the nodes child with the data */

for ( index = 0; index < 5; index++ )
if ( xmlStrEqual( attrname, keys[ index ] ) )
{
get_item_values( node->children,
ellipseinfo + index * max_rows, max_rows );
return;
}
}

I find iterating over an array of keywords easier to read than
a lot if if-else constructs. And the intent in your code seemed
to be to find a certain key and then fill the corresponding co-
lumn in all rows with the data for this keyword. Due to the layout
of the array in FORTRAN all data for a keyword are in continuousd
memory locations, i.e. in memory the array looks somewhat like
this:

Lat0 Lat1 Lat2 Lat3 ..... LatMax
Lon0 Lon1 Lon2 Lon3 ..... LonMax
LatR0 LatR1 LatR2 LatR3 ..... LatRMax
LonR0 LonR1 LonR2 LonR3 ..... LonRMax
Ori0 Ori1 Ori2 Ori3 ..... OriMax

each line with 'max_rows' elements. With index == 0 one wants
to read in the all the latitudes, so we pass 'ellipseinfo'
directly to the get_item_values() function. With index == 1
we want to read the longitudes, so we pass the address of
where the data for longitudes belong to, i.e. the 'max_rows'-th
element, 'ellipseinfo + max_rows' etc. (you could replace

ellipseinfo + max_rows

with

&ellipseinfo[ max_rows ]

which is exactly the same.

Now the function that does the actual reading becomes:

static void
get_item_values( xmlNode * node,
double * dest,
int max_rows )
{
int cur_row = 0;

/* Keep reading until we either have as many items as fit into the
array or the end of the data column has been reached */

while ( cur_row < max_rows && node )
{
xmlChar *attrname;

if ( node->type != XML_ELEMENT_NODE
|| ! xmlStrcmp( node->name, ( const xmlChar * ) "item" )
|| ! ( attrname =
xmlGetNoNsProp( node, ( const xmlChar * ) "value" ) ) )
return;

dest[ cur_row++ ] = xmlXPathCastStringToNumber( attrname );
node = node->next;
}
}

This assumes that the XML file has a structure somewhat like this:

<datacolumn name="Latitude">
<item value="21.9408"/>
<item value="19.7698"/>
<item value="43.5629"/>
</datacolumn>
<datacolumn name="Longitude">
<item value="13.1234"/>
<item value="21.6229"/>
<item value="56.2322"/>
</datacolumn>
....

And here's a version that looks more like yours but requires a
C99 compliant compiler:

static void
get_item_values( xmlNode * node,
int max_rows,
double dest[ max_rows ] )
{
int cur_row = 0;

while ( cur_row < max_rows && node )
{
xmlChar *attrname;

if ( node->type != XML_ELEMENT_NODE
|| ! xmlStrcmp( node->name, ( const xmlChar * ) "item" )
|| ! ( attrname =
xmlGetNoNsProp( node, ( const xmlChar * ) "value" ) ) )
return;

dest[ cur_row++ ] = xmlXPathCastStringToNumber( attrname );
node = node->next;
}
}

static void
read_datacolumn( xmlNode * node,
int max_rows,
double ellipseinfo[ 5 ][ max_rows ] )
{
xmlChar *attrname;
int index;
const xmlChar * keys[ ] = { ( const xmlChar * ) "Latitude",
( const xmlChar * ) "Longitude",
( const xmlChar * ) "LatRadius",
( const xmlChar * ) "LonRadius",
( const xmlChar * ) "Orientation" };

if ( ! xmlStrcmp( node->name, ( const xmlChar * ) "datacolumn" )
|| ! ( attrname = xmlGetNoNsProp( node, ( const xmlChar * ) "name" ) ) )
return;

for ( index = 0; index < 5; index++ )
if ( xmlStrEqual( attrname, keys[ index ] ) )
{
get_item_values( node->children, max_rows, ellipseinfo[ index ] );
return;
}
}

static void
print_element_names( xmlNode * node,
int max_rows,
double ellipseinfo[ 5 ][ max_rows ] )
{
while ( node )
{
if ( node->type == XML_ELEMENT_NODE )
read_datacolumn( node, max_rows, ellipseinfo );
else
print_element_names( node->children, max_rows, ellipseinfo );

node = node->next;
}
}

void
read_xml( int * max,
char * filename,
double ellipseinfo[ 5 ][ *max ] )
{
xmlDoc *doc;
xmlNode *root_element;

LIBXML_TEST_VERSION

doc = xmlReadFile( filename, NULL, 0 );

if ( doc == NULL ) {
fprintf( stderr, "error: could not parse file %s\n", filename );
return;
}

root_element = xmlDocGetRootElement( doc );

if ( root_element == NULL ) {
fprintf( stderr, "error: could not get root element\n" );
return;
}

print_element_names( root_element, *max, ellipseinfo );

xmlFreeDoc( doc );
xmlCleanupParser( );
}

With this you should be able to call the read_xml() function with
three arguments, first the number of rows, then the file name and
finally the array you want to get filled in. But keep in mind that
this is all untested since I don't have an XML file that I could
use for testing. And there's also a bit more of error checking
missing as well as returning an indication if reading worked out
correctly...
Regards, Jens
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,581
Members
45,056
Latest member
GlycogenSupporthealth

Latest Threads

Top