libxml2 get xsd validation errors - c

I'm using xmlTextReader to process large xml files. Now i need to validate the instance against an xsd schema. The api from libxml2 is a little bit confusing, how this is done.
With my approach, im getting the validation errors in the schemaParseErrorHandler function, but without any line numbers or column numbers.
How can i get these informations?
#include <stdio.h>
#include <libxml/xmlreader.h>
#include <libxml/encoding.h>
#include <libxml/xmlwriter.h>
static void schemaParseErrorHandler(void *arg, xmlErrorPtr error)
{
fprintf( stderr, "Error at line %d, column %d\n%s",
error->line, error->int2, error->message);
*((bool*)arg) = true;
}
int main( int argc, char **argv )
{
xmlInitParser();
xmlSchemaPtr schema = NULL;
xmlSchemaParserCtxtPtr schema_parser_ctxt = NULL;
int has_schema_errors = 0;
int ret = -1;
xmlSchemaValidCtxtPtr valid_ctxt = NULL;
if ((schema_parser_ctxt = xmlSchemaNewParserCtxt("example.xsd")))
{
schema = xmlSchemaParse(schema_parser_ctxt);
xmlSchemaFreeParserCtxt(schema_parser_ctxt);
if (schema)
{
valid_ctxt = xmlSchemaNewValidCtxt(schema);
}
}
xmlTextReaderPtr reader = NULL;
reader = xmlReaderForFile(filename, RPCXmlStream::STD_ENCODING, 0);
if (reader != NULL)
{
if (valid_ctxt)
{
xmlTextReaderSchemaValidateCtxt(reader, valid_ctxt, 0);
xmlSchemaSetValidStructuredErrors(valid_ctxt, schemaParseErrorHandler, &has_schema_errors);
}
ret = xmlTextReaderRead(reader);
while (ret == 1 && !has_schema_errors)
{
//... procesing informations
ret = xmlTextReaderRead(reader);
}
}
if (ret != 0)
{
xmlErrorPtr err = xmlGetLastError();
TRACE("%s: failed to parse in line %d, col %d. Error %d: %s\n",
err->file,
err->line,
err->int2,
err->code,
err->message);
}
xmlFreeTextReader(reader);
xmlCleanupParser();
return 0;
}
Another try was to use the function
xmlTextReaderSchemaValidate(reader, "example.xsd");
instead of creating an xmlSchemaNewValidCtxt, but than the programm is crashing on the first call to xmlTextReaderRead.
So how is validation done right, so that the error informations includes line and column numbers?

So, your questions got me thinking and when I looked in the libxml2 documentation,
Structure xmlError
struct _xmlError {
int domain : What part of the library raised this er
int code : The error code, e.g. an xmlParserError
char * message : human-readable informative error messag
xmlErrorLevel level : how consequent is the error
char * file : the filename
int line : the line number if available
char * str1 : extra string information
char * str2 : extra string information
char * str3 : extra string information
int int1 : extra number information
int int2 : error column # or 0 if N/A (todo: renam
void * ctxt : the parser context if available
void * node : the node in the tree
}
where we can clearly see that the xmlErrorPtr which is returned by the function xmlGetLastError() clearly contains information about the filename and the line number and the column number.
char * file : the filename
int line : the line number if available
...
int int2 : error column
So to test if this was possible or not, here is the code that I used (basically your code with minor changes to make it run on my system):
#include <stdio.h>
#include <stdbool.h>
#include <libxml/xmlreader.h>
#include <libxml/encoding.h>
#include <libxml/xmlwriter.h>
static void schemaParseErrorHandler(void *arg, xmlErrorPtr error)
{
fprintf(stderr, "Error at line %d, column %d\n%s", error->line, error->int2, error->message);
*((bool*)arg) = true;
}
int main( int argc, char **argv )
{
xmlInitParser();
xmlSchemaPtr schema = NULL;
xmlSchemaParserCtxtPtr schema_parser_ctxt = NULL;
int has_schema_errors = 0;
int ret = -1;
xmlSchemaValidCtxtPtr valid_ctxt = NULL;
if ((schema_parser_ctxt = xmlSchemaNewParserCtxt("/home/junglefox/shiporder.xsd")))
{
schema = xmlSchemaParse(schema_parser_ctxt);
xmlSchemaFreeParserCtxt(schema_parser_ctxt);
if (schema)
{
valid_ctxt = xmlSchemaNewValidCtxt(schema);
}
}
xmlTextReaderPtr reader = NULL;
const char* filename = "/home/junglefox/shiporder.xml";
reader = xmlReaderForFile(filename, /*RPCXmlStream::STD_ENCODING,*/ NULL, 0);
if (reader != NULL)
{
if (valid_ctxt)
{
xmlTextReaderSchemaValidateCtxt(reader, valid_ctxt, 0);
xmlSchemaSetValidStructuredErrors(valid_ctxt, schemaParseErrorHandler, &has_schema_errors);
}
ret = xmlTextReaderRead(reader);
while (ret == 1 && !has_schema_errors)
{
//... procesing informations
ret = xmlTextReaderRead(reader);
}
}
if (ret != 0)
{
xmlErrorPtr err = xmlGetLastError();
fprintf(stdout, "%s: failed to parse in line %d, col %d. Error %d: %s\n",
err->file,
err->line,
err->int2,
err->code,
err->message);
}
xmlFreeTextReader(reader);
xmlCleanupParser();
return 0;
}
where, the shiporder.xml and shiporder.xsd used in that program were copied from the url and saved locally.
I compiled and ran the code like this:
junglefox#ubuntu:~$ gcc -o test_xsd main.c -I/usr/include/libxml2/ -lxml2 -L/usr/lib/x86_64-linux-gnu/
junglefox#ubuntu:~$ ./test_xsd
junglefox#ubuntu:~$
The output this time was nothing. As it should be as there were no errors.
If however now I make an intentional error in the shiporder.xml file, as shown below:
Here is the partial-snippet from the buggy shiporder.xml:
<?xml version="1.0" encoding="UTF-8"?>
...
<item>
<title>Hide your heart</title>
<quantity>1</quantity>
price>9.90</price>
</item>
</shiporder>
Notice the missing < before price!
Now I run the program again,
junglefox#ubuntu:~$ ./test_xsd
Error at line 22, column 0
Element 'item': Character content other than whitespace is not allowed because the content type is 'element-only'.
which answers your question(s):
With my approach, im getting the validation errors in the schemaParseErrorHandler function, but without any line numbers or column numbers. How can i get these informations?
and,
So how is validation done right, so that the error informations includes line and column numbers?
as the output clearly shows the line number 22 and column 0, where there was an unexpected empty space due to the missing <.

Related

Segmentation fault when parsing group elemnt with libconfig

I'm trying to write C code to parse a config file using libconfig
The config file contains a simple element and a group. A group is composed of multiple settings, each has a unique name. ref
Config file :
host_name = "HOST";
device_settings:
{
rcu1:
{
product_id = 0x0001;
vendor_id = 0x0217;
},
rcu2:
{
product_id = 0x0001;
vendor_id = 0x0218;
}
}
I want to parse all RCUs data and store it in a data structre (the storing part is not a problem for now).
So I'm using the simple steps of :
Store the group in a config_setting_t * called section.
get length of section in a varaible called len
Iterrate len time to read RCUs data.
The problem is when i want to read RCU data i get a seg fault.
Code :
#include <libconfig.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int main()
{
config_t cfg;
config_setting_t *root;
config_setting_t *section;
config_setting_t *elem;
int d, len;
config_init(&cfg);
if (config_read_file(&cfg,"./config.cfg") != CONFIG_TRUE) {
printf("[%s:%d] %s \n", config_error_file(&cfg),
config_error_line(&cfg), config_error_text(&cfg));
config_destroy(&cfg);
return -1;
}
if ((root = config_root_setting(&cfg)) == NULL) {
printf ("[%s:%d] %s \n", config_error_file(&cfg),
config_error_line(&cfg), config_error_text(&cfg));
config_destroy(&cfg);
return -1;
}
/* Device settings */
if ((section = config_setting_get_member(root, "device_settings")) != NULL)
{
len = config_setting_length(section);
printf("len = %d \n",len);
}
int i;
const char* device_id;
config_setting_t *device = NULL;
printf("device_settings %s a group \n",config_setting_is_group(section)?"is":"isn't");
for(i=0;i<len;i++) {
printf("iteration i = %d \n",i);
//device
if(device = config_setting_get_elem(section, i) != NULL) {
/*device id*/
if ((d = config_setting_lookup_string(device, "device_id",&device_id) != CONFIG_FALSE)) /*seg fault here*/
{
// Do stuff
}
}
}
return 0;
}
Something strange I noticed is when I compile the code i get this warning :
parse.c: In function ‘main’: parse.c:46:14: warning: assignment to
‘config_setting_t *’ {aka ‘struct config_setting_t *’} from ‘int’
makes pointer from integer without a cast [-Wint-conversion]
if(device = config_setting_get_elem(section, i) != NULL) {
GDB output :
Program received signal SIGSEGV, Segmentation fault.
0x00007ffff7da78a0 in config_setting_get_member () from
/lib/x86_64-linux-gnu/libconfig.so.9
ref to config_setting_get_elem(..)
I can not find what wrong Im doing. Everything looks correct to me.
Can someone see why the seg fault is happening?
if (device = config_setting_get_elem(section, i) != NULL)
needs to be
if ((device = config_setting_get_elem(section, i)) != NULL)
Because != has higher precedence than =.

Inserting many records

The output of the program generates random results for a 100 rounds of 100 coin tosses. I need to get all of those H & T results (10,000) into SQLite for analysis.
Round 1:
TTTTTHHTHTTHHTTTHHTTTTTTTHHTTHHHHHHTTTHTHTTHHTTTHHHHHHTHTTTTHTHHTHTTTHTHTHTHTTHHTTTTTTHTHTTHHTTTTHTH
-
Round 99:
TTHHHTHTHHTTTHHTTHTHTHTTHHHHHTHTTTTHHHHTHTHTHTHHHHTTTTTHTTHHHTTTTHTTHHHHTTTTTTHHTHTTHTTTTHTHHTTHHTHT
Round 100:
THTHTHHHHHTTHTTTTTTTTTTTHTTHHTHHHTHHTHHHHTTHTHHTTHTHTHHTTHHHTHTHHTHTTTTTHTHTTHHTHTHHHTHTHHTHTHHTTTHH
I have little knowledge how to do this, so I looked into what others have done. Apparently Multi-row INSERT is not supported. Some sources say the only way to insert several rows in a batch is use a Select statement. How would I achieve this?
Assuming the database and table is already created, what code could I use in a C program to insert all this data into SQLite?
/* This file was mechanically generated from tests/check-pcg32.c */
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <time.h>
#include <string.h>
#include "pcg_basic.h"
int main(int argc, char** argv)
{
// Read command-line options
int rounds = 1000;
bool nondeterministic_seed = false;
int round, i;
++argv;
--argc;
if (argc > 0 && strcmp(argv[0], "-r") == 0) {
nondeterministic_seed = true;
++argv;
--argc;
}
if (argc > 0) {
rounds = atoi(argv[0]);
}
// In this version of the code, we'll use a local rng, rather than the
// global one.
pcg32_random_t rng;
// You should *always* seed the RNG. The usual time to do it is the
// point in time when you create RNG (typically at the beginning of the
// program).
//
// pcg32_srandom_r takes two 64-bit constants (the initial state, and the
// rng sequence selector; rngs with different sequence selectors will
// *never* have random sequences that coincide, at all) - the code below
// shows three possible ways to do so.
if (nondeterministic_seed) {
// Seed with external entropy -- the time and some program addresses
// (which will actually be somewhat random on most modern systems).
// A better solution, entropy_getbytes, using /dev/random, is provided
// in the full library.
pcg32_srandom_r(&rng, time(NULL) ^ (intptr_t)&printf,
(intptr_t)&rounds);
} else {
// Seed with a fixed constant
pcg32_srandom_r(&rng, 42u, 54u);
}
printf("pcg32_random_r:\n"
" - result: 32-bit unsigned int (uint32_t)\n"
" - period: 2^64 (* 2^63 streams)\n"
" - state type: pcg32_random_t (%zu bytes)\n"
" - output func: XSH-RR\n"
"\n",
sizeof(pcg32_random_t));
for (round = 1; round <= rounds; ++round) {
printf("Round %d:\n", round);
/* Make some 32-bit numbers */
printf(" 32bit:");
for (i = 0; i < 6; ++i)
printf(" 0x%08x", pcg32_random_r(&rng));
printf("\n");
/* Toss some coins */
printf(" Coins: ");
for (i = 0; i < 100; ++i)
printf("%c", pcg32_boundedrand_r(&rng, 2) ? 'H' : 'T');
printf("\n");
printf("\n");
}
return 0;
}
I guess I would start with a simple approach and if you need to access results based on rounds and tosses, your table in the database could consist of three fields, e.g. round, toss and result.
To create the database and the table, you could use the command line program sqlite3 as follows:
sqlite3 random.sqlite
Then enter the following command at the command prompt:
CREATE TABLE experiment (round INT, toss INT, result CHAR(1));
Now you have created a database random.sqlite and a table experiment.
How to fill this table from C?
One way would be:
open db
create a prepared insert statement
start a transaction
in a loop:
bind values to parameters
run the SQL
reset prepared statement
end transaction
finalize the statement and close the db to avoid resource leaks
A simple example of the flow described could look like this:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "sqlite3.h"
void exit_with_error(sqlite3 *db, const char * msg) {
fprintf(stderr, "%s: %s\n", msg, sqlite3_errmsg(db));
sqlite3_close(db);
exit(1);
}
char *pathToDB = "<a path to random.sqlite>";
sqlite3 *open_db(void);
sqlite3_stmt *prepeare_stmt(sqlite3 *db);
void close_db(sqlite3 *db, sqlite3_stmt *stmt);
int main() {
sqlite3 *db = open_db();
sqlite3_stmt *stmt = prepeare_stmt(db);
if(sqlite3_exec(db, "BEGIN TRANSACTION", NULL, NULL, NULL) != SQLITE_OK) {
exit_with_error(db, "begin transaction failed");
}
char *result = "H";
for(int round = 0; round < 100; round++) {
for(int toss = 0; toss < 100; toss++) {
//for a short test simply output alternately "H" and "T"
result = strcmp(result, "H") == 0 ? "T" : "H";
//bind values to parameters
sqlite3_bind_int(stmt, 1, round);
sqlite3_bind_int(stmt, 2, toss);
sqlite3_bind_text(stmt, 3, result, -1, SQLITE_STATIC);
//run the SQL
if (sqlite3_step(stmt) != SQLITE_DONE) {
exit_with_error(db, "insert failed");
}
//reset prepared statement to be able to bind new values in next loop pass
if (sqlite3_reset(stmt) != SQLITE_OK) {
exit_with_error(db, "reset failed");
}
}
}
if(sqlite3_exec(db, "END TRANSACTION", NULL, NULL, NULL) != SQLITE_OK) {
exit_with_error(db, "end transaction failed");
}
//finalize the stmt and close db to avoid resource leaks
sqlite3_finalize(stmt);
sqlite3_close(db);
return 0;
}
sqlite3 *open_db(void) {
sqlite3 *db;
if (sqlite3_open(pathToDB, &db) != SQLITE_OK) {
exit_with_error(db, "can't open db: ");
}
return db;
}
sqlite3_stmt *prepeare_stmt(sqlite3 *db) {
sqlite3_stmt *stmt;
//create a prepared statement
int rc = sqlite3_prepare_v2(db, "INSERT INTO experiment VALUES (?1,?2,?3)", -1, &stmt, 0);
if (rc != SQLITE_OK) {
exit_with_error(db, "failure preparing insert");
}
return stmt;
}
Note: Instead of using random values, the code simply outputs 'T' and 'H' alternately to have a simple test case.
After executing the program, you can take a look at the stored values with the command line as follows:
sqlite3 random.sqlite
At the sqlite command prompt you can enter:
SELECT * FROM experiment;
The following should then be output in the console:
0|0|T
0|1|H
0|2|T
...
99|97|H
99|98|T
99|99|H
To get the value for the third toss in the first round you would issue the following sql statement from the command prompt:
SELECT * from experiment WHERE round=0 and toss=2;
that would output something like this on the console:
0|2|T

C: How to access value returned by Net-SNMP GET

I apologize for the naive question, Iam new to Net-SNMP. I have tried using this simple SNMP demo app given in Net-SNMP website.
This code performs a SNMP-GET and manipulates the response to check if the value returned is a ASN_OCTET_STRING, and if yes, access the string using vars->val.string and assigned to a character pointer sp.
But Iam unable to figure out how to access this value if the type is anything other than ASN_OCTET_STRING. For example how do I take this value and, say, assign it to a variable if it is of type 'ASN_INTEGER' or 'ASN_OBJECT_ID'.
#include <net-snmp/net-snmp-config.h>
#include <net-snmp/net-snmp-includes.h>
#include <string.h>
#define DEMO_USE_SNMP_VERSION_3
#ifdef DEMO_USE_SNMP_VERSION_3
const char *our_v3_passphrase = "MD5Password";
#endif
int main(int argc, char ** argv)
{
netsnmp_session session, *ss;
netsnmp_pdu *pdu;
netsnmp_pdu *response;
oid anOID[MAX_OID_LEN];
size_t anOID_len;
netsnmp_variable_list *vars;
int status;
int count=1;
init_snmp("snmpdemoapp");
snmp_sess_init( &session );
session.peername = strdup("localhost:161");
#ifdef DEMO_USE_SNMP_VERSION_3
session.version=SNMP_VERSION_3;
session.securityName = strdup("user2");
session.securityNameLen = strlen(session.securityName);
session.securityLevel = SNMP_SEC_LEVEL_AUTHNOPRIV;
session.securityAuthProto = usmHMACMD5AuthProtocol;
session.securityAuthProtoLen = sizeof(usmHMACMD5AuthProtocol)/sizeof(oid);
session.securityAuthKeyLen = USM_AUTH_KU_LEN;
if (generate_Ku(session.securityAuthProto,
session.securityAuthProtoLen,
(u_char *) our_v3_passphrase, strlen(our_v3_passphrase),
session.securityAuthKey,
&session.securityAuthKeyLen) != SNMPERR_SUCCESS) {
snmp_perror(argv[0]);
snmp_log(LOG_ERR,
"Error generating Ku from authentication pass phrase. \n");
exit(1);
}
#else /* we'll use the insecure (but simplier) SNMPv1 */
session.version = SNMP_VERSION_1;
session.community = "demopublic";
session.community_len = strlen(session.community);
#endif /* SNMPv1 */
SOCK_STARTUP;
ss = snmp_open(&session);
if (!ss) {
snmp_sess_perror("ack", &session);
SOCK_CLEANUP;
exit(1);
}
pdu = snmp_pdu_create(SNMP_MSG_GET);
anOID_len = MAX_OID_LEN;
if (!snmp_parse_oid("ip.21.1.8.xx.xx.xx.xx", anOID, &anOID_len)) {
snmp_perror("ip.21.1.8.xx.xx.xx.xx");
SOCK_CLEANUP;
exit(1);
}
snmp_add_null_var(pdu, anOID, anOID_len);
status = snmp_synch_response(ss, pdu, &response);
if (status == STAT_SUCCESS && response->errstat == SNMP_ERR_NOERROR) {
for(vars = response->variables; vars; vars = vars->next_variable)
print_variable(vars->name, vars->name_length, vars);
/* manipuate the information ourselves */
for(vars = response->variables; vars; vars = vars->next_variable) {
if (vars->type == ASN_OCTET_STR) {
char *sp = (char *)malloc(1 + vars->val_len);
memcpy(sp, vars->val.string, vars->val_len);
sp[vars->val_len] = '\0';
printf("value #%d is a string: %s\n", count++, sp); //Here sp now has the string - But this doesnt work when the string is for eg."HOST-RESOURCES-MIB::hrSWInstalledDate.1953 = STRING: 0-1-1,0:0:0.0"
free(sp);
}
else if(vars->type == ASN_INTEGER) {
printf("value is an Integer\n");
int ObjVal;
// How do I get the Integer value and assign it to 'ObjVal'
}
else if(vars->type == ASN_OBJECT_ID) {
printf("value is an OID\n");
// How do I get the OID and assign it to some variable
}
else if(vars->type == ASN_TIMETICKS) {
printf("value is in Timeticks\n");
// How do I get the Timeticks and assign it to some variable for further processing
}
}
} else {
if (status == STAT_SUCCESS)
fprintf(stderr, "Error in packet\nReason: %s\n",
snmp_errstring(response->errstat));
else if (status == STAT_TIMEOUT)
fprintf(stderr, "Timeout: No response from %s.\n",
session.peername);
else
snmp_sess_perror("snmpdemoapp", ss);
}
if (response)
snmp_free_pdu(response);
snmp_close(ss);
SOCK_CLEANUP;
return (0);
}
Tried vars->val.integer or vars->val.object_id, but that doesnot contain the value. What am I missing here?
My another question is, even when it is of type ASN_OCTET_STRING, when the GET reply is something like this,
HOST-RESOURCES-MIB::hrSWInstalledDate.1953 = STRING: 0-1-1,0:0:0.0
then vars->val.string doesnt have "0-1-1,0:0:0.0" as string.
Basically my question is How does the value get stored in the response structure from which I can retrieve the values?
Thanks in Advance!!
P.S: Makefile link from Net-SNMP website.
Edit1:
For Integers, i can read using *vars->val->string as pointed out by immibis. Any Ideas about how to access other datatypes?
As you can see in /usr/include/net-snmp/types.h file or similar on your system, net-snmp vars->val has the following union type:
typedef union {
long *integer;
u_char *string;
oid *objid;
u_char *bitstring;
struct counter64 *counter64;
#ifdef NETSNMP_WITH_OPAQUE_SPECIAL_TYPES
float *floatVal;
double *doubleVal;
/*
* t_union *unionVal;
*/
#endif /* NETSNMP_WITH_OPAQUE_SPECIAL_TYPES */
} netsnmp_vardata;
also *vars has val_len field, where the length of data stored.
So you can access integer as *vars->val.integer, string as pointer to u_char vars->val.string with vars->val_len chars, oid as pointer to oid vars->val.objid with vars->val_len/sizeof(oid) oid elements and so on.

The following code doesn't work .. why?

The following code isn't working as expected ..
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdbool.h>
struct dest
{
char filename[20], keyword[20];
bool opened;
FILE * stream;
};
void display_data(const struct dest p) {
printf("Keyword: %s, Filename: %s, Used: %s\n", p.keyword, p.filename, p.opened ? "Yes" : "No");
}
int main(int argc, char const *argv[])
{
// float lon, lat;
// char info[80];
if ((argc+1) % 2) {
fprintf(stderr, "Usage: %s file_to_read file_for_unknown type file type file ...\n", argv[0]);
return 2;
}
if (access(argv[1], F_OK) == -1) {
fprintf(stderr, "File can't be accessed: %s\n", argv[1]);
return 2;
}
const short pairs = (argc-3)/2;
struct dest data[pairs];
short times = 4;
for(short i = 4; i < argc; i += 2) {
struct dest data[i-times];
data[i-times].opened = false;
strcpy(data[i-times].keyword, argv[i-1]);
strcpy(data[i-times].filename, argv[i]);
// display_data(data[i-times]);
times += 1;
}
display_data(data[0]);
return 0;
}
That's what happens when I try to run it ..
./categorize spooky.csv other.csv UFO UFOS.csv
Keyword: ?, Filename: �#, Used: No
Which isn't that meaningful ..
I have been trying to work out the solution .. in vein ..
I don't understand where the problem is ..
Arguments are parsed as follows:
The first argument: the file the program is supposed to read from (ignored for now)
The second argument: the file the program is supposed to store at any unknown info found in the spooky.csv file (also, ignored in this implementation)
The other arguments: they are parsed as pairs, the first is the keyword, the second is the file ..
My Solution for this filtering problem was to create an array of structs, and within each struct I store the keyword, the filename and the file io stream (which i am ignoring, for now) ..
Any help would be highly appreciated ..
You have 2 struct dest data[] arrays. The inner one is masking the outer - get rid of it.
Your compiler is probably warning about this, if you have warnings turned on.

The Code doesn't print the expected output, why?

The following code doesn't behave as expected ..
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdbool.h>
struct dest
{
char filename[20], keyword[20];
bool opened;
FILE * file;
};
void display_data(const struct dest p) {
printf("Keyword: %s, Filename: %s, Used: %s\n", p.keyword, p.filename, p.opened ? "Yes" : "No");
}
int main(int argc, char const *argv[])
{
// declaring variables
float lon, lat;
char info[80];
FILE *reader;
// checking required arguments
if ((argc+1) % 2 || argc < 2) {
fprintf(stderr, "Usage: %s file_to_read file_for_unknown type file type file ...\n", argv[0]);
return 2;
}
// opening the reader
if (!(reader = fopen(argv[1], "r"))) {
fprintf(stderr, "File can't be accessed: %s\n", argv[1]);
return 2;
}
// creating important globals
const short pairs = (argc-3)/2;
struct dest data[pairs];
struct dest other;
strcpy(other.filename, argv[2]);
other.opened = false;
// gathering data
short times = 4;
for(short i = 4; i < argc; i += 2) {
data[i-times].opened = false;
strcpy(data[i-times].keyword, argv[i-1]);
strcpy(data[i-times].filename, argv[i]);
times += 1;
}
// finally, scanning the file ..
struct dest *use_f; // pointer for the wanted destination ..
bool known;
while (fscanf(reader, "%f,%f,%79[^\n]", &lat, &lon, info)) {
// deciding which file to use ..
known = false;
for(short i=0; i < pairs; ++i) {
if (strstr(info, data[i].keyword)) {
known = true;
use_f = &data[i];
}
}
if (!(known)) {
use_f = &other;
}
// checking the file ..
if (!((*use_f).opened)) {
(*use_f).file = fopen((*use_f).filename, "w");
(*use_f).opened = true;
}
// writing to the file ..
fprintf((*use_f).file, "%f,%f,%s\n", lat, lon, info);
}
// closing all data streams, and informing user ..
for (short i=0; i < pairs; ++i) {
display_data(data[i]);
if (data[i].opened) {
fclose(data[i].file);
data[i].opened = false;
}
}
fclose(reader);
fclose(other.file);
return 0;
}
The command used to run it is this ..
./categorize spooky.csv other.csv UFO UFOS.csv # I get no output at all
It seems that the while loop doesn't actually end, which is mysterious, because the file (spooky.csv) is only 11 lines !
30.685163,-68.137207,Type=Yeti
28.304380,-74.575195,Type=UFO
29.132971,-71.136475,Type=Ship
28.343065,-62.753906,Type=Elvis
27.868217,-68.005371,Type=Goatsucker
30.496017,-73.333740,Type=Disappearance
26.224447,-71.477051,Type=UFO
29.401320,-66.027832,Type=Ship
37.879536,-69.477539,Type=Elvis
22.705256,-68.192139,Type=Elvis
27.166695,-87.484131,Type=Elvis
It just keeps writing to other.file, yet I don't know why ..
The program simply doesn't end, can anybody explain things to me ?
From the fscanf() manpage: "The value EOF is returned if an input failure occurs before any conversion (such as an end-of-file) occurs."
Here's a hint... EOF isn't equal to 0. Your while-loop never terminates.

Resources