added Info.plist
[TestXSLT.git] / libxml2 / doc / parsedecl.py
1 #!/usr/bin/python -u
2 #
3 # tries to parse the output of gtk-doc declaration files and make
4 # an XML reusable description from them
5 #
6 import sys
7 import string
8
9 ids = {}
10
11 macros = {}
12 variables = {}
13 structs = {}
14 typedefs = {}
15 enums = {}
16 functions = {}
17 user_functions = {}
18 ret_types = {}
19 types = {}
20
21 sections = []
22 files = {}
23 identifiers_file = {}
24 identifiers_type = {}
25
26 ##################################################################
27 #
28 #          Indexer to generate the word index
29 #
30 ##################################################################
31 index = {}
32
33
34 def indexString(id, str):
35     if str == None:
36         return
37     str = string.replace(str, "'", ' ')
38     str = string.replace(str, '"', ' ')
39     str = string.replace(str, "/", ' ')
40     str = string.replace(str, '*', ' ')
41     str = string.replace(str, "[", ' ')
42     str = string.replace(str, "]", ' ')
43     str = string.replace(str, "(", ' ')
44     str = string.replace(str, ")", ' ')
45     str = string.replace(str, "<", ' ')
46     str = string.replace(str, '>', ' ')
47     str = string.replace(str, "&", ' ')
48     str = string.replace(str, '#', ' ')
49     str = string.replace(str, ",", ' ')
50     str = string.replace(str, '.', ' ')
51     str = string.replace(str, ';', ' ')
52     tokens = string.split(str)
53     for token in tokens:
54         try:
55             c = token[0]
56             if string.find(string.letters, c) < 0:
57                 pass
58             elif len(token) < 3:
59                 pass
60             else:
61                 lower = string.lower(token)
62                 # TODO: generalize this a bit
63                 if lower == 'and' or lower == 'the':
64                     pass
65                 elif index.has_key(token):
66                     index[token].append(id)
67                 else:
68                     index[token] = [id]
69         except:
70             pass
71        
72
73
74 ##################################################################
75 #
76 #          Parsing: libxml-decl.txt
77 #
78 ##################################################################
79 def mormalizeTypeSpaces(raw, function):
80     global types
81
82     tokens = string.split(raw)
83     type = ''
84     for token in tokens:
85         if type != '':
86             type = type + ' ' + token
87         else:
88             type = token
89     if types.has_key(type):
90         types[type].append(function)
91     else:
92         types[type] = [function]
93     return type
94
95 def removeComments(raw):
96     while string.find(raw, '/*') > 0:
97         e = string.find(raw, '/*')
98         tmp = raw[0:e]
99         raw = raw[e:]
100         e = string.find(raw, '*/')
101         if e > 0:
102             raw = tmp + raw[e + 2:]
103         else:
104             raw = tmp
105     return raw
106
107 def extractArgs(raw, function):
108     raw = removeComments(raw)
109     raw = string.replace(raw, '\n', ' ')
110     raw = string.replace(raw, '\r', ' ')
111     list = string.split(raw, ",")
112     ret = []
113     for arg in list:
114         i = len(arg)
115         if i == 0:
116             continue
117         i = i - 1
118         c = arg[i]
119         while string.find(string.letters, c) >= 0 or \
120               string.find(string.digits, c) >= 0 or c == '_':
121             i = i - 1
122             if i < 0:
123                 break
124             c = arg[i]
125         name = arg[i+1:]
126         while string.find(string.whitespace, c) >= 0:
127             i = i - 1
128             if i < 0:
129                 break
130             c = arg[i]
131         type = mormalizeTypeSpaces(arg[0:i+1], function)
132         if name == 'void' and type == '':
133             pass
134         else:
135             ret.append([type, name, ''])
136
137     return ret
138
139 def extractTypes(raw, function):
140     global ret_types
141
142     tokens = string.split(raw)
143     type = ''
144     for token in tokens:
145         if type != '':
146             type = type + ' ' + token
147         else:
148             type = token
149     if ret_types.has_key(type):
150         ret_types[type].append(function)
151     else:
152         ret_types[type] = [function]
153
154     return type
155
156 def parseMacro():
157     global input
158     global macros
159     global variables
160
161     var = 1
162     line = input.readline()[:-1]
163     while line != "</MACRO>":
164         if line[0:6] == "<NAME>" and line[-7:] == "</NAME>":
165             name = line[6:-7]
166         elif string.find(line, "#define") >= 0:
167             var = 0
168         line = input.readline()[:-1]
169
170     if var == 1:
171         variables[name] = ['', ''] # type, info
172         identifiers_type[name] = "variable"
173     else:
174         macros[name] = [[], ''] # args, info
175         identifiers_type[name] = "macro"
176
177 def parseStruct():
178     global input
179     global structs
180
181     line = input.readline()[:-1]
182     while line != "</STRUCT>":
183         if line[0:6] == "<NAME>" and line[-7:] == "</NAME>":
184             name = line[6:-7]
185         line = input.readline()[:-1]
186
187     structs[name] = ''
188     identifiers_type[name] = "struct"
189
190 def parseTypedef():
191     global input
192     global typedefs
193
194     line = input.readline()[:-1]
195     while line != "</TYPEDEF>":
196         if line[0:6] == "<NAME>" and line[-7:] == "</NAME>":
197             name = line[6:-7]
198         line = input.readline()[:-1]
199
200     typedefs[name] = ''
201     identifiers_type[name] = "typedef"
202
203 def parseEnum():
204     global input
205     global enums
206
207     line = input.readline()[:-1]
208     consts = []
209     while line != "</ENUM>":
210         if line[0:6] == "<NAME>" and line[-7:] == "</NAME>":
211             name = line[6:-7]
212         elif string.find(line, 'enum') >= 0:
213             pass
214         elif string.find(line, '{') >= 0:
215             pass
216         elif string.find(line, '}') >= 0:
217             pass
218         elif string.find(line, ';') >= 0:
219             pass
220         else:
221             comment = string.find(line, '/*')
222             if comment >= 0:
223                 line = line[0:comment]
224             decls = string.split(line, ",")
225             for decl in decls:
226                 val = string.split(decl, "=")[0]
227                 tokens = string.split(val)
228                 if len(tokens) >= 1:
229                     token = tokens[0]
230                     if string.find(string.letters, token[0]) >= 0:
231                         consts.append(token)
232                         identifiers_type[token] = "const"
233         line = input.readline()[:-1]
234         
235     enums[name] = [consts, '']
236     identifiers_type[name] = "enum"
237
238 def parseStaticFunction():
239     global input
240     global user_functions
241
242     line = input.readline()[:-1]
243     type = None
244     signature = ""
245     while line != "</USER_FUNCTION>":
246         if line[0:6] == "<NAME>" and line[-7:] == "</NAME>":
247             name = line[6:-7]
248         elif line[0:9] == "<RETURNS>" and line[-10:] == "</RETURNS>":
249             type = extractTypes(line[9:-10], name)
250         else:
251             signature = signature + line
252         line = input.readline()[:-1]
253
254     args = extractArgs(signature, name)
255     user_functions[name] = [[type, ''] , args, '']
256     identifiers_type[name] = "functype"
257
258 def parseFunction():
259     global input
260     global functions
261
262     line = input.readline()[:-1]
263     type = None
264     signature = ""
265     while line != "</FUNCTION>":
266         if line[0:6] == "<NAME>" and line[-7:] == "</NAME>":
267             name = line[6:-7]
268         elif line[0:9] == "<RETURNS>" and line[-10:] == "</RETURNS>":
269             type = extractTypes(line[9:-10], name)
270         else:
271             signature = signature + line
272         line = input.readline()[:-1]
273
274     args = extractArgs(signature, name)
275     functions[name] = [[type, ''] , args, '']
276     identifiers_type[name] = "function"
277
278 print "Parsing: libxml-decl.txt"
279 input = open('libxml-decl.txt')
280 while 1:
281     line = input.readline()
282     if not line:
283         break
284     line = line[:-1]
285     if line == "<MACRO>":
286         parseMacro()
287     elif line == "<ENUM>":
288         parseEnum()
289     elif line == "<FUNCTION>":
290         parseFunction()
291     elif line == "<STRUCT>":
292         parseStruct()
293     elif line == "<TYPEDEF>":
294         parseTypedef()
295     elif line == "<USER_FUNCTION>":
296         parseStaticFunction()
297     elif len(line) >= 1 and line[0] == "<":
298         print "unhandled %s" % (line)
299
300 print "Parsed: %d macros. %d structs, %d typedefs, %d enums" % (
301           len(macros.keys()), len(structs.keys()), len(typedefs.keys()),
302           len(enums))
303 c = 0
304 for enum in enums.keys():
305     consts = enums[enum][0]
306     c = c + len(consts)
307 print "        %d variables, %d constants, %d functions and %d functypes" % (
308           len(variables.keys()), c, len(functions.keys()),
309           len(user_functions.keys()))
310 print "The functions manipulates %d different types" % (len(types.keys()))
311 print "The functions returns %d different types" % (len(ret_types.keys()))
312
313 ##################################################################
314 #
315 #          Parsing: libxml-decl-list.txt
316 #
317 ##################################################################
318 def parseSection():
319     global input
320     global sections
321     global files
322     global identifiers_file
323
324     tokens = []
325     line = input.readline()[:-1]
326     while line != "</SECTION>":
327         if line[0:6] == "<FILE>" and line[-7:] == "</FILE>":
328             name = line[6:-7]
329         elif len(line) > 0:
330             tokens.append(line)
331         line = input.readline()[:-1]
332
333     sections.append(name)
334     files[name] = tokens
335     for token in tokens:
336         identifiers_file[token] = name
337         #
338         # Small transitivity for enum values
339         #
340         if enums.has_key(token):
341             for const in enums[token][0]:
342                 identifiers_file[const] = name
343
344 print "Parsing: libxml-decl-list.txt"
345 input = open('libxml-decl-list.txt')
346 while 1:
347     line = input.readline()
348     if not line:
349         break
350     line = line[:-1]
351     if line == "<SECTION>":
352         parseSection()
353     elif len(line) >= 1 and line[0] == "<":
354         print "unhandled %s" % (line)
355
356 print "Parsed: %d files %d identifiers" % (len(files), len(identifiers_file.keys()))
357 ##################################################################
358 #
359 #          Parsing: xml/*.xml
360 #          To enrich the existing info with extracted comments
361 #
362 ##################################################################
363
364 nbcomments = 0
365
366 def insertParameterComment(id, name, value, is_param):
367     global nbcomments
368
369     indexString(id, value)
370     if functions.has_key(id):
371         if is_param == 1:
372             args = functions[id][1]
373             found = 0
374             for arg in args:
375                 if arg[1] == name:
376                     arg[2] = value
377                     found = 1
378                     break
379             if found == 0 and name != '...':
380                 print "Arg %s not found on function %s description" % (name, id)
381                 return
382         else:
383             ret = functions[id][0]
384             ret[1] = value
385     elif user_functions.has_key(id):
386         if is_param == 1:
387             args = user_functions[id][1]
388             found = 0
389             for arg in args:
390                 if arg[1] == name:
391                     arg[2] = value
392                     found = 1
393                     break
394             if found == 0 and name != '...':
395                 print "Arg %s not found on functype %s description" % (name, id)
396                 print args
397                 return
398         else:
399             ret = user_functions[id][0]
400             ret[1] = value
401     elif macros.has_key(id):
402         if is_param == 1:
403             args = macros[id][0]
404             found = 0
405             for arg in args:
406                 if arg[0] == name:
407                     arg[1] = value
408                     found = 1
409                     break
410             if found == 0:
411                 args.append([name, value])
412         else:
413             print "Return info for macro %s: %s" % (id, value)
414 #           ret = macros[id][0]
415 #           ret[1] = value
416     else:
417         print "lost specific comment %s: %s: %s" % (id, name, value)
418         return
419     nbcomments = nbcomments + 1
420
421 def insertComment(name, title, value, id):
422     global nbcomments
423
424     ids[name] = id
425     indexString(name, value)
426     if functions.has_key(name):
427         functions[name][2] = value
428         return "function"
429     elif typedefs.has_key(name):
430         typedefs[name] = value
431         return "typedef"
432     elif macros.has_key(name):
433         macros[name][1] = value
434         return "macro"
435     elif variables.has_key(name):
436         variables[name][1] = value
437         return "variable"
438     elif structs.has_key(name):
439         structs[name] = value
440         return "struct"
441     elif enums.has_key(name):
442         enums[name][1] = value
443         return "enum"
444     elif user_functions.has_key(name):
445         user_functions[name][2] = value
446         return "user_function"
447     else:
448         print "lost comment %s: %s" % (name, value)
449         return "unknown"
450     nbcomments = nbcomments + 1
451
452
453 import libxml2
454 import os
455
456
457 def analyzeXMLDescriptionRow(doc, desc, id, row):
458     if doc == None or desc == None or id == None or row == None:
459         return
460     ctxt = doc.xpathNewContext()
461     ctxt.setContextNode(row)
462     param = ctxt.xpathEval("entry[1]/parameter")
463     entries = ctxt.xpathEval("entry")
464     if param == []:
465         is_param = 0
466         name = None
467     else:
468         name = param[0].content
469         is_param = 1
470     str = entries[1].content
471     str = string.replace(str, '\n', ' ')
472     str = string.replace(str, '\r', ' ')
473     str = string.replace(str, '    ', ' ')
474     str = string.replace(str, '   ', ' ')
475     str = string.replace(str, '  ', ' ')
476     while len(str) >= 1 and str[0] == ' ':
477         str=str[1:]
478
479     insertParameterComment(id, name, str, is_param)
480
481
482
483 def analyzeXMLDescription(doc, desc):
484     if doc == None or desc == None:
485         return
486     ctxt = doc.xpathNewContext()
487     ctxt.setContextNode(desc)
488
489     #
490     # get the function name
491     #
492     try:
493         title = ctxt.xpathEval("title")[0].content
494     except:
495         return
496     old_id = ctxt.xpathEval("string(title/anchor/@id)")
497     id = string.replace(title, '(', ' ');
498     id = string.replace(id, ')', ' ');
499     id = string.split(id) [0]
500
501     #
502     # get the function comments
503     #
504     comment = ""
505     paras = ctxt.xpathEval("para")
506     for para in paras:
507         str = para.content
508         str = string.replace(str, '\n', ' ')
509         str = string.replace(str, '\r', ' ')
510         str = string.replace(str, '    ', ' ')
511         str = string.replace(str, '   ', ' ')
512         str = string.replace(str, '  ', ' ')
513         while len(str) >= 1 and str[0] == ' ':
514             str=str[1:]
515
516         comment = comment + str
517
518     insertComment(id, title, comment, old_id)
519
520     rows = ctxt.xpathEval("informaltable/tgroup/tbody/row")
521     for row in rows:
522         analyzeXMLDescriptionRow(doc, desc, id, row)
523
524 def analyzeXMLDoc(doc):
525     if doc == None:
526         return
527     ctxt = doc.xpathNewContext()
528     descriptions = ctxt.xpathEval("//refsect2")
529     print len(descriptions)
530     for description in descriptions:
531         analyzeXMLDescription(doc, description)
532
533 xmlfiles = 0
534 filenames = os.listdir("xml")
535 for filename in filenames:
536     print filename
537     try:
538         doc = libxml2.parseFile("xml/" + filename)
539         analyzeXMLDoc(doc)
540         doc.freeDoc()
541         xmlfiles = xmlfiles + 1
542     except:
543         print "failed to parse XML description %s" % ("xml/" + filename)
544         continue
545
546 print "Parsed: %d XML files collexting %d comments" % (xmlfiles, nbcomments)
547
548 ##################################################################
549 #
550 #          Saving: libxml2-api.xml
551 #
552 ##################################################################
553
554 def escape(raw):
555     raw = string.replace(raw, '&', '&amp;')
556     raw = string.replace(raw, '<', '&lt;')
557     raw = string.replace(raw, '>', '&gt;')
558     raw = string.replace(raw, "'", '&apos;')
559     raw = string.replace(raw, '"', '&quot;')
560     return raw
561
562 print "Saving XML description libxml2-api.xml"
563 output = open("libxml2-api.xml", "w")
564 output.write('<?xml version="1.0" encoding="ISO-8859-1"?>\n')
565 output.write("<api name='libxml2'>\n")
566 output.write("  <files>\n")
567 for file in files.keys():
568     output.write("    <file name='%s'>\n" % file)
569     for symbol in files[file]:
570         output.write("     <exports symbol='%s'/>\n" % (symbol))
571     output.write("    </file>\n")
572 output.write("  </files>\n")
573
574 output.write("  <symbols>\n")
575 symbols=macros.keys()
576 for i in structs.keys(): symbols.append(i)
577 for i in variables.keys(): variables.append(i)
578 for i in typedefs.keys(): symbols.append(i)
579 for i in enums.keys():
580     symbols.append(i)
581     for j in enums[i][0]:
582         symbols.append(j)
583 for i in functions.keys(): symbols.append(i)
584 for i in user_functions.keys(): symbols.append(i)
585 symbols.sort()
586 prev = None
587 for i in symbols:
588     if i == prev:
589 #        print "Symbol %s redefined" % (i)
590         continue
591     else:
592         prev = i
593     if identifiers_type.has_key(i):
594         type = identifiers_type[i]
595         
596         if identifiers_file.has_key(i):
597             file = identifiers_file[i]
598         else:
599             file = None
600
601         output.write("    <%s name='%s'" % (type, i))
602         if file != None:
603             output.write(" file='%s'" % (file))
604         if type == "function":
605            output.write(">\n");
606            (ret, args, doc) = functions[i]
607            if doc != None and doc != '':
608                output.write("      <info>%s</info>\n" % (escape(doc)))
609            if ret[1] != None and ret[1] != '':
610                output.write("      <return type='%s' info='%s'/>\n" % (
611                             ret[0], escape(ret[1])))
612            else:
613                if ret[0] != 'void' and\
614                   ret[0][0:4] != 'void': # This one is actually a bug in GTK Doc
615                    print "Description for return on %s is missing" % (i)
616                output.write("      <return type='%s'/>\n" % (ret[0]))
617            for arg in args:
618                if arg[2] != None and arg[2] != '':
619                    output.write("      <arg name='%s' type='%s' info='%s'/>\n" %
620                                 (arg[1], arg[0], escape(arg[2])))
621                else:
622                    if arg[0] != '...':
623                        print "Description for %s on %s is missing" % (arg[1], i)
624                    output.write("      <arg name='%s' type='%s'/>\n" % (
625                                 arg[1], arg[0]))
626            output.write("    </%s>\n" % (type));
627         elif type == 'functype':
628            output.write(">\n");
629            (ret, args, doc) = user_functions[i]
630            if doc != None and doc != '':
631                output.write("      <info>%s</info>\n" % (escape(doc)))
632            if ret[1] != None and ret[1] != '':
633                output.write("      <return type='%s' info='%s'/>\n" % (
634                             ret[0], escape(ret[1])))
635            else:
636                if ret[0] != 'void' and\
637                   ret[0][0:4] != 'void': # This one is actually a bug in GTK Doc
638                    print "Description for return on %s is missing" % (i)
639                output.write("      <return type='%s'/>\n" % (ret[0]))
640            for arg in args:
641                if arg[2] != None and arg[2] != '':
642                    output.write("      <arg name='%s' type='%s' info='%s'/>\n" %
643                                 (arg[1], arg[0], escape(arg[2])))
644                else:
645                    if arg[0] != '...':
646                        print "Description for %s on %s is missing" % (arg[1], i)
647                    output.write("      <arg name='%s' type='%s'/>\n" % (
648                                 arg[1], arg[0]))
649            output.write("    </%s>\n" % (type));
650         elif type == 'macro':
651            output.write(">\n");
652            if macros[i][1] != None and macros[i][1] != '':
653                output.write("      <info>%s</info>\n" % (escape(macros[i][1])))
654            else:
655                print "Description for %s is missing" % (i)
656            args = macros[i][0]
657            for arg in args:
658                if arg[1] != None and arg[1] != '':
659                    output.write("      <arg name='%s' info='%s'/>\n" %
660                                 (arg[0], escape(arg[1])))
661                else:
662                    print "Description for %s on %s is missing" % (arg[1], i)
663                    output.write("      <arg name='%s'/>\n" % (arg[0]))
664            output.write("    </%s>\n" % (type));
665         elif type == 'struct':
666            if structs[i] != None and structs[i] != '':
667                output.write(" info='%s'/>\n" % (escape(structs[i])))
668            else:
669                output.write("/>\n");
670         elif type == 'variable':
671            if variables[i][1] != None and variables[i][1] != '':
672                output.write(" info='%s'/>\n" % (escape(variables[i])))
673            else:
674                output.write("/>\n");
675         elif type == 'typedef':
676            if typedefs[i] != None and typedefs[i] != '':
677                output.write(" info='%s'/>\n" % (escape(typedefs[i])))
678            else:
679                output.write("/>\n");
680         else:
681            output.write("/>\n");
682     else:
683         print "Symbol %s not found in identifiers list" % (i)
684 output.write("  </symbols>\n")
685 output.write("</api>\n")
686 output.close()
687 print "generated XML for %d symbols" % (len(symbols))
688
689 ##################################################################
690 #
691 #          Saving: libxml2-api.xml
692 #
693 ##################################################################
694
695 hash = {}
696 for file in files.keys():
697     for symbol in files[file]:
698         hash[symbol] = file
699
700 def link(id):
701     if ids.has_key(id):
702         target = string.upper(ids[id])
703     else:
704         target = string.upper(id)
705     if hash.has_key(id):
706         module = string.lower(hash[id])
707     else:
708         module = 'index'
709     file = 'html/libxml-' + module + '.html';
710     return file + '#' + target
711     
712 print "Saving XML crossreferences libxml2-refs.xml"
713 output = open("libxml2-refs.xml", "w")
714 output.write('<?xml version="1.0" encoding="ISO-8859-1"?>\n')
715 output.write("<apirefs name='libxml2'>\n")
716 output.write("  <references>\n")
717 typ = ids.keys()
718 typ.sort()
719 for id in typ:
720     output.write("    <reference name='%s' href='%s'/>\n" % (id, link(id)))
721 output.write("  </references>\n")
722 output.write("  <alpha>\n")
723 letter = None
724 ids = ids.keys()
725 ids.sort()
726 for id in ids:
727     if id[0] != letter:
728         if letter != None:
729             output.write("    </letter>\n")
730         letter = id[0]
731         output.write("    <letter name='%s'>\n" % (letter))
732     output.write("    <ref name='%s'/>\n" % (id))
733 if letter != None:
734     output.write("    </letter>\n")
735 output.write("  </alpha>\n")
736 output.write("  <constructors>\n")
737 typ = ret_types.keys()
738 typ.sort()
739 for type in typ:
740     if type == '' or type == 'void' or type == "int" or type == "char *" or \
741        type == "const char *" :
742         continue
743     output.write("    <type name='%s'>\n" % (type))
744     ids = ret_types[type]
745     for id in ids:
746         output.write("      <ref name='%s'/>\n" % (id))
747     output.write("    </type>\n")
748 output.write("  </constructors>\n")
749 output.write("  <functions>\n")
750 typ = types.keys()
751 typ.sort()
752 for type in typ:
753     if type == '' or type == 'void' or type == "int" or type == "char *" or \
754        type == "const char *" :
755         continue
756     output.write("    <type name='%s'>\n" % (type))
757     ids = types[type]
758     for id in ids:
759         output.write("      <ref name='%s'/>\n" % (id))
760     output.write("    </type>\n")
761 output.write("  </functions>\n")
762
763 output.write("  <files>\n")
764 typ = files.keys()
765 typ.sort()
766 for file in typ:
767     output.write("    <file name='%s'>\n" % (file))
768     for id in files[file]:
769         output.write("      <ref name='%s'/>\n" % (id))
770     output.write("    </file>\n")
771 output.write("  </files>\n")
772
773 output.write("  <index>\n")
774 typ = index.keys()
775 typ.sort()
776 letter = None
777 count = 0
778 chunk = 0
779 chunks = []
780 for id in typ:
781     if len(index[id]) > 30:
782         continue
783     if id[0] != letter:
784         if letter == None or count > 200:
785             if letter != None:
786                 output.write("      </letter>\n")
787                 output.write("    </chunk>\n")
788                 count = 0
789                 chunks.append(["chunk%s" % (chunk -1), first_letter, letter])
790             output.write("    <chunk name='chunk%s'>\n" % (chunk))
791             first_letter = id[0]
792             chunk = chunk + 1
793         elif letter != None:
794             output.write("      </letter>\n")
795         letter = id[0]
796         output.write("      <letter name='%s'>\n" % (letter))
797     output.write("        <word name='%s'>\n" % (id))
798     tokens = index[id];
799     tokens.sort()
800     tok = None
801     for token in index[id]:
802         if tok == token:
803             continue
804         tok = token
805         output.write("          <ref name='%s'/>\n" % (token))
806         count = count + 1
807     output.write("        </word>\n")
808 if letter != None:
809     output.write("      </letter>\n")
810     output.write("    </chunk>\n")
811     output.write("    <chunks>\n")
812     for ch in chunks:
813         output.write("      <chunk name='%s' start='%s' end='%s'/>\n" % (
814                      ch[0], ch[1], ch[2]))
815     output.write("    </chunks>\n")
816 output.write("  </index>\n")
817
818 output.write("</apirefs>\n")
819 output.close()