2 * An XML parser for Wikipedia Data dumps.
3 * Converts XML files to tab-separated values files readable by spreadsheets
4 * and statistical packages.
15 // timestamp of the form 2003-11-07T00:43:23Z
16 #define DATE_LENGTH 10
18 #define TIMESTAMP_LENGTH 20
20 #define MEGABYTE 1048576
21 #define FIELD_BUFFER_SIZE 1024
22 // 2048 KB in bytes + 1
23 //#define TEXT_BUFFER_SIZE 2097153
24 //#define TEXT_BUFFER_SIZE 10485760
27 TITLE, ARTICLEID, REVISION, REVID, TIMESTAMP, CONTRIBUTOR,
28 EDITOR, EDITORID, MINOR, COMMENT, UNUSED, TEXT
31 enum block { TITLE_BLOCK, REVISION_BLOCK, CONTRIBUTOR_BLOCK, SKIP };
33 enum outtype { FULL, SIMPLE };
37 // pointers to once-allocated buffers
50 // track string size of the elements, to prevent O(N^2) processing in charhndl
51 // when we have to take strlen for every character which we append to the buffer
53 size_t articleid_size;
57 size_t timestamp_size;
66 enum elements element;
68 enum outtype output_type;
73 /* free_data and clean_data
74 * Takes a pointer to the data struct and an integer {0,1} indicating if the
75 * title data needs to be cleared as well.
76 * Also, frees memory dynamically allocated to store data.
79 clean_data(revisionData *data, int title)
81 // reset title (if we are switching articles)
83 data->title[0] = '\0';
84 data->articleid[0] = '\0';
86 data->articleid_size = 0;
90 data->revid[0] = '\0';
93 data->timestamp[0] = '\0';
95 data->editor[0] = '\0';
96 data->editorid[0] = '\0';
97 data->comment[0] = '\0';
100 // reset length tracking
101 data->revid_size = 0;
104 data->timestamp_size = 0;
106 data->editor_size = 0;
107 data->editorid_size = 0;
108 data->comment_size = 0;
111 // reset flags and element type info
113 data->element = UNUSED;
119 free_data(revisionData *data, int title)
122 //printf("freeing article\n");
124 free(data->articleid);
129 free(data->timestamp);
132 free(data->editorid);
137 void cleanup_revision(revisionData *data) {
141 void cleanup_article(revisionData *data) {
147 init_data(revisionData *data, outtype output_type)
149 data->text = (char*) malloc(4 * MEGABYTE); // 2MB is the article length limit, 4MB is 'safe'?
150 data->comment = (char*) malloc(FIELD_BUFFER_SIZE);
151 data->title = (char*) malloc(FIELD_BUFFER_SIZE);
152 data->articleid = (char*) malloc(FIELD_BUFFER_SIZE);
153 data->revid = (char*) malloc(FIELD_BUFFER_SIZE);
154 data->date = (char*) malloc(FIELD_BUFFER_SIZE);
155 data->time = (char*) malloc(FIELD_BUFFER_SIZE);
156 data->timestamp = (char*) malloc(FIELD_BUFFER_SIZE);
157 data->anon = (char*) malloc(FIELD_BUFFER_SIZE);
158 data->editor = (char*) malloc(FIELD_BUFFER_SIZE);
159 data->editorid = (char*) malloc(FIELD_BUFFER_SIZE);
162 // resets the data fields, null terminates strings, sets lengths
165 data->output_type = output_type;
168 /* for debugging only, prints out the state of the data struct
171 print_state(revisionData *data)
173 printf("element = %i\n", data->element);
174 printf("output_type = %i\n", data->output_type);
175 printf("title = %s\n", data->title);
176 printf("articleid = %s\n", data->articleid);
177 printf("revid = %s\n", data->revid);
178 printf("date = %s\n", data->date);
179 printf("time = %s\n", data->time);
180 printf("anon = %s\n", data->anon);
181 printf("editor = %s\n", data->editor);
182 printf("editorid = %s\n", data->editorid);
183 printf("minor = %s\n", (data->minor ? "1" : "0"));
184 printf("comment = %s\n", data->comment);
185 printf("text = %s\n", data->text);
190 /* Write a header for the comma-separated output
195 // printf("title, articleid, revid, date, time, anon, editor, editorid, minor, comment\n");
196 // printf("title\tarticleid\trevid\tdate time\tanon\teditor\teditorid\tminor\n");
202 * write a line of comma-separated value formatted data to standard out
204 * title,articleid,revid,date,time,anon,editor,editorid,minor,comment
205 * (str) (int) (int) (str)(str)(bin)(str) (int) (bin) (str)
207 * it is called right before cleanup_revision() and cleanup_article()
210 write_row(revisionData *data)
213 // TODO: make it so you can specify fields to output
214 // note that date and time are separated by a space, to match postgres's
216 printf("%s\t%s\t%s\t%s %s\t%s\t%s\t%s\t%s",
222 (data->editor[0] != '\0') ? "0" : "1", // anon?
225 (data->minor) ? "1" : "0");
226 switch (data->output_type)
229 printf("\t%i\t%f\n", (unsigned int) strlen(data->text), shannon_H(data->text, data->text_size));
233 printf("\t%s\t%s\n", data->comment, data->text);
240 split_timestamp(revisionData *data)
242 char *t = data->timestamp;
243 strncpy(data->date, data->timestamp, DATE_LENGTH);
244 char *timeinstamp = &data->timestamp[DATE_LENGTH+1];
245 strncpy(data->time, timeinstamp, TIME_LENGTH);
248 // like strncat but with previously known length
250 strlcatn(char *dest, const char *src, size_t dest_len, size_t n)
252 //size_t dest_len = strlen(dest);
255 for (i = 0 ; i < n && src[i] != '\0' ; i++)
256 dest[dest_len + i] = src[i];
257 dest[dest_len + i] = '\0';
263 charhndl(void* vdata, const XML_Char* s, int len)
265 revisionData* data = (revisionData*) vdata;
266 if (data->element != UNUSED && data->position != SKIP) {
269 //t[len] = '\0'; // makes t a well-formed string
270 switch (data->element) {
272 // printf("buffer length = %i, text: %s\n", len, t);
273 strlcatn(data->text, s, data->text_size, len);
274 data->text_size += len;
277 strlcatn(data->comment, s, data->comment_size, len);
278 data->comment_size += len;
281 strlcatn(data->title, s, data->title_size, len);
282 data->title_size += len;
285 // printf("articleid = %s\n", t);
286 strlcatn(data->articleid, s, data->articleid_size, len);
287 data->articleid_size += len;
290 // printf("revid = %s\n", t);
291 strlcatn(data->revid, s, data->revid_size, len);
292 data->revid_size += len;
295 strlcatn(data->timestamp, s, data->timestamp_size, len);
296 data->timestamp_size += len;
297 if (strlen(data->timestamp) == TIMESTAMP_LENGTH)
298 split_timestamp(data);
301 strlcatn(data->editor, s, data->editor_size, len);
302 data->editor_size += len;
305 //printf("editorid = %s\n", t);
306 strlcatn(data->editorid, s, data->editorid_size, len);
307 data->editorid_size += len;
309 /* the following are implied or skipped:
311 printf("found minor element\n"); doesn't work
312 break; minor tag is just a tag
321 start(void* vdata, const XML_Char* name, const XML_Char** attr)
323 revisionData* data = (revisionData*) vdata;
325 if (strcmp(name,"title") == 0) {
326 cleanup_article(data); // cleans up data from last article
327 data->element = TITLE;
328 data->position = TITLE_BLOCK;
329 } else if (data->position != SKIP) {
330 if (strcmp(name,"revision") == 0) {
331 data->element = REVISION;
332 data->position = REVISION_BLOCK;
333 } else if (strcmp(name, "contributor") == 0) {
334 data->element = CONTRIBUTOR;
335 data->position = CONTRIBUTOR_BLOCK;
336 } else if (strcmp(name,"id") == 0)
337 switch (data->position) {
339 data->element = ARTICLEID;
342 data->element = REVID;
344 case CONTRIBUTOR_BLOCK:
345 data->element = EDITORID;
349 // minor tag has no character data, so we parse here
350 else if (strcmp(name,"minor") == 0) {
351 data->element = MINOR;
354 else if (strcmp(name,"timestamp") == 0)
355 data->element = TIMESTAMP;
357 else if (strcmp(name, "username") == 0)
358 data->element = EDITOR;
360 else if (strcmp(name,"ip") == 0)
361 data->element = EDITORID;
363 else if (strcmp(name,"comment") == 0)
364 data->element = COMMENT;
366 else if (strcmp(name,"text") == 0)
367 data->element = TEXT;
369 else if (strcmp(name,"page") == 0
370 || strcmp(name,"mediawiki") == 0
371 || strcmp(name,"restrictions") == 0
372 || strcmp(name,"siteinfo") == 0)
373 data->element = UNUSED;
380 end(void* vdata, const XML_Char* name)
382 revisionData* data = (revisionData*) vdata;
383 if (strcmp(name, "revision") == 0 && data->position != SKIP) {
384 write_row(data); // crucial... :)
385 cleanup_revision(data); // also crucial
387 data->element = UNUSED; // sets our state to "not-in-useful"
388 } // thus avoiding unpleasant character data
389 // b/w tags (newlines etc.)
392 void print_usage(char* argv[]) {
393 fprintf(stderr, "usage: <wikimedia dump xml> | %s [options]\n", argv[0]);
394 fprintf(stderr, "\n");
395 fprintf(stderr, "options:\n");
396 fprintf(stderr, " -t print text and comments after each line of tab separated data\n");
397 fprintf(stderr, "\n");
398 fprintf(stderr, "Takes a wikimedia data dump XML stream on standard in, and produces\n");
399 fprintf(stderr, "a tab-separated stream of revisions on standard out:\n");
400 fprintf(stderr, "\n");
401 fprintf(stderr, "title, articleid, revid, date, time, anon, editor, editorid, minor, revlength\n");
402 fprintf(stderr, "\n");
403 fprintf(stderr, "author: Erik Garrison <erik@hypervolu.me>\n");
408 main(int argc, char *argv[])
411 enum outtype output_type;
413 // in "simple" output, we don't print text and comments
414 output_type = SIMPLE;
417 while ((c = getopt(argc, argv, "ht")) != -1)
432 if (dry_run) { // lets us print initialization options
433 printf("simple_output = %i\n", output_type);
437 // create a new instance of the expat parser
438 XML_Parser parser = XML_ParserCreate("UTF-8");
440 // initialize the user data struct which is passed to callback functions
442 // initialize the elements of the struct to default values
443 init_data(&data, output_type);
446 // makes the parser pass "data" as the first argument to every callback
447 XML_SetUserData(parser, &data);
448 void (*startFnPtr)(void*, const XML_Char*, const XML_Char**) = start;
449 void (*endFnPtr)(void*, const XML_Char*) = end;
450 void (*charHandlerFnPtr)(void*, const XML_Char*, int) = charhndl;
452 // sets start and end to be the element start and end handlers
453 XML_SetElementHandler(parser, startFnPtr, endFnPtr);
454 // sets charhndl to be the callback for character data
455 XML_SetCharacterDataHandler(parser, charHandlerFnPtr);
460 // shovel data into the parser
463 // read into buf a bufferfull of data from standard input
464 size_t len = fread(buf, 1, BUFSIZ, stdin);
465 done = len < BUFSIZ; // checks if we've got the last bufferfull
467 // passes the buffer of data to the parser and checks for error
468 // (this is where the callbacks are invoked)
469 if (XML_Parse(parser, buf, len, done) == XML_STATUS_ERROR) {
472 XML_ErrorString(XML_GetErrorCode(parser)),
473 (int) XML_GetCurrentLineNumber(parser));
479 XML_ParserFree(parser);