2 # python script to generate configoptions.cpp and config.doc from config.xml
4 # Copyright (C) 1997-2015 by Dimitri van Heesch.
6 # Permission to use, copy, modify, and distribute this software and its
7 # documentation under the terms of the GNU General Public License is hereby
8 # granted. No representations are made about the suitability of this software
9 # for any purpose. It is provided "as is" without express or implied warranty.
10 # See the GNU General Public License for more details.
12 # Documents produced by Doxygen are derivative works derived from the
13 # input used in their production; they are not affected by this license.
15 import xml.dom.minidom
19 from xml.dom import minidom, Node
21 def transformDocs(doc):
22 # join lines, unless it is an empty line
23 # remove doxygen layout constructs
25 doc = doc.replace("\n", " ")
26 doc = doc.replace("\r", " ")
27 doc = doc.replace("\t", " ")
28 doc = doc.replace("\\&", "&")
29 doc = doc.replace("(\\c ", "(")
30 doc = doc.replace("\\c ", " ")
31 doc = doc.replace("\\b ", " ")
32 doc = doc.replace("\\e ", " ")
33 doc = doc.replace("\\$", "$")
34 doc = doc.replace("\\#include ", "#include ")
35 doc = doc.replace("\\#undef ", "#undef ")
36 doc = doc.replace("-# ", "\n - ")
37 doc = doc.replace(" - ", "\n - ")
38 doc = doc.replace("\\sa", "\nSee also: ")
39 doc = doc.replace("\\par", "\n")
40 doc = doc.replace("@note", "\nNote:")
41 doc = doc.replace("\\note", "\nNote:")
42 doc = doc.replace("\\verbatim", "\n")
43 doc = doc.replace("\\endverbatim", "\n")
44 doc = doc.replace("<code>", "")
45 doc = doc.replace("</code>", "")
46 doc = doc.replace("`", "")
47 doc = doc.replace("\\<", "<")
48 doc = doc.replace("\\>", ">")
49 doc = doc.replace("\\@", "@")
50 doc = doc.replace("\\\\", "\\")
51 # \ref name "description" -> description
52 doc = re.sub('\\\\ref +[^ ]* +"([^"]*)"', '\\1', doc)
54 # \ref <key> -> description
55 doc = re.sub('\\\\ref +doxygen_usage', '"Doxygen usage"', doc)
56 doc = re.sub('\\\\ref +extsearch', '"External Indexing and Searching"',
58 doc = re.sub('\\\\ref +external', '"Linking to external documentation"',
60 # fallback for not handled
61 doc = re.sub('\\\\ref', '', doc)
62 #<a href="address">description</a> -> description (see: address)
63 doc = re.sub('<a +href="([^"]*)" *>([^<]*)</a>', '\\2 (see: \\1)', doc)
64 # LaTeX name as formula -> LaTeX
65 doc = doc.replace("\\f$\\mbox{\\LaTeX}\\f$", "LaTeX")
66 # Other formula's (now just 2) so explicitly mentioned.
67 doc = doc.replace("\\f$2^{(16+\\mbox{LOOKUP\\_CACHE\\_SIZE})}\\f$",
68 "2^(16+LOOKUP_CACHE_SIZE)")
69 doc = doc.replace("\\f$2^{16} = 65536\\f$", "2^16=65536")
70 # remove consecutive spaces
71 doc = re.sub(" +", " ", doc)
72 # a dirty trick to get an extra empty line in Doxyfile documentation.
73 # <br> will be removed later on again, we need it here otherwise splitlines
74 # will filter the extra line.
75 doc = doc.replace("<br>", "\n<br>\n")
76 # a dirty trick to go to the next line in Doxyfile documentation.
77 # <br/> will be removed later on again, we need it here otherwise splitlines
78 # will filter the line break.
79 doc = doc.replace("<br/>", "\n<br/>\n")
81 doc = doc.splitlines()
84 split_doc += textwrap.wrap(line, 78)
85 # replace \ by \\, replace " by \", and ' ' by a newline with end string
86 # and start string at next line
88 for line in split_doc:
89 if (line.strip() != "<br/>"):
90 docC.append(line.strip().replace('\\', '\\\\').
91 replace('"', '\\"').replace("<br>", ""))
95 def collectValues(node):
97 for n in node.childNodes:
98 if (n.nodeName == "value"):
99 if n.nodeType == Node.ELEMENT_NODE:
100 if n.getAttribute('name') != "":
101 if n.getAttribute('show_docu') != "NO":
102 name = "<code>" + n.getAttribute('name') + "</code>"
103 desc = n.getAttribute('desc')
110 def addValues(var, node):
111 for n in node.childNodes:
112 if (n.nodeName == "value"):
113 if n.nodeType == Node.ELEMENT_NODE:
114 name = n.getAttribute('name')
115 print(" %s->addValue(\"%s\");" % (var, name))
118 def parseHeader(node,objName):
120 for n in node.childNodes:
121 if n.nodeType == Node.ELEMENT_NODE:
122 if (n.nodeName == "docs"):
123 if (n.getAttribute('doxyfile') != "0"):
125 docC = transformDocs(doc)
126 print(" %s->setHeader(" % (objName))
130 if i != rng - 1: # since we go from 0 to rng-1
131 print(" \"%s\\n\"" % (line))
133 print(" \"%s\"" % (line))
138 type = node.getAttribute('type')
139 format = node.getAttribute('format')
140 defval = node.getAttribute('defval')
141 adefval = node.getAttribute('altdefval')
143 if (type != 'obsolete'):
144 for n in node.childNodes:
145 if (n.nodeName == "docs"):
146 if (n.getAttribute('doxyfile') != "0"):
147 if n.nodeType == Node.ELEMENT_NODE:
150 values = collectValues(node)
151 doc += "<br/>Possible values are: "
156 doc += "%s and " % (val)
160 doc += "%s, " % (val)
162 doc += "<br/>The default value is: <code>%s</code>." % (defval)
163 elif (type == 'int'):
164 minval = node.getAttribute('minval')
165 maxval = node.getAttribute('maxval')
166 doc += "<br/>%s: %s, %s: %s, %s: %s." % (" Minimum value", minval,
167 "maximum value", maxval,
168 "default value", defval)
169 elif (type == 'bool'):
170 if (node.hasAttribute('altdefval')):
171 doc += "<br/>%s: %s." % ("The default value is", "system dependent")
173 doc += "<br/>%s: %s." % ("The default value is", "YES" if (defval == "1") else "NO")
174 elif (type == 'list'):
175 if format == 'string':
176 values = collectValues(node)
181 doc += "%s and " % (val)
185 doc += "%s, " % (val)
186 elif (type == 'string'):
189 doc += "<br/>The default directory is: <code>%s</code>." % (
191 elif format == 'file':
192 abspath = node.getAttribute('abspath')
195 doc += "<br/>The default file is: <code>%s</code>." % (
198 doc += "<br/>%s: %s%s%s." % (
199 "The default file (with absolute path) is",
200 "<code>",defval,"</code>")
203 doc += "<br/>The file has to be specified with full path."
204 elif format =='image':
205 abspath = node.getAttribute('abspath')
208 doc += "<br/>The default image is: <code>%s</code>." % (
211 doc += "<br/>%s: %s%s%s." % (
212 "The default image (with absolute path) is",
213 "<code>",defval,"</code>")
216 doc += "<br/>The image has to be specified with full path."
217 else: # format == 'string':
219 doc += "<br/>The default value is: <code>%s</code>." % (
222 if (node.hasAttribute('depends')):
223 depends = node.getAttribute('depends')
224 doc += "<br/>%s \\ref cfg_%s \"%s\" is set to \\c YES." % (
225 "This tag requires that the tag", depends.lower(), depends.upper())
227 docC = transformDocs(doc)
230 def parseOption(node):
231 # Handling part for Doxyfile
232 name = node.getAttribute('id')
233 type = node.getAttribute('type')
234 format = node.getAttribute('format')
235 defval = node.getAttribute('defval')
236 adefval = node.getAttribute('altdefval')
237 depends = node.getAttribute('depends')
238 setting = node.getAttribute('setting')
239 docC = prepCDocs(node);
241 print("#if %s" % (setting))
250 print(" cb = cfg->addBool(")
251 print(" \"%s\"," % (name))
255 if i != rng - 1: # since we go from 0 to rng-1
256 print(" \"%s\\n\"" % (line))
258 print(" \"%s\"," % (line))
259 print(" %s" % (enabled))
262 print(" cb->addDependency(\"%s\");" % (depends))
263 elif type == 'string':
264 print(" cs = cfg->addString(")
265 print(" \"%s\"," % (name))
269 if i != rng - 1: # since we go from 0 to rng-1
270 print(" \"%s\\n\"" % (line))
272 print(" \"%s\"" % (line))
275 print(" cs->setDefaultValue(\"%s\");" % (defval.replace('\\','\\\\')))
277 print(" cs->setWidgetType(ConfigString::File);")
278 elif format == 'image':
279 print(" cs->setWidgetType(ConfigString::Image);")
280 elif format == 'dir':
281 print(" cs->setWidgetType(ConfigString::Dir);")
283 print(" cs->addDependency(\"%s\");" % (depends))
285 print(" ce = cfg->addEnum(")
286 print(" \"%s\"," % (name))
290 if i != rng - 1: # since we go from 0 to rng-1
291 print(" \"%s\\n\"" % (line))
293 print(" \"%s\"," % (line))
294 print(" \"%s\"" % (defval))
296 addValues("ce", node)
298 print(" ce->addDependency(\"%s\");" % (depends))
300 minval = node.getAttribute('minval')
301 maxval = node.getAttribute('maxval')
302 print(" ci = cfg->addInt(")
303 print(" \"%s\"," % (name))
307 if i != rng - 1: # since we go from 0 to rng-1
308 print(" \"%s\\n\"" % (line))
310 print(" \"%s\"," % (line))
311 print(" %s,%s,%s" % (minval, maxval, defval))
314 print(" ci->addDependency(\"%s\");" % (depends))
316 print(" cl = cfg->addList(")
317 print(" \"%s\"," % (name))
321 if i != rng - 1: # since we go from 0 to rng-1
322 print(" \"%s\\n\"" % (line))
324 print(" \"%s\"" % (line))
326 addValues("cl", node)
328 print(" cl->addDependency(\"%s\");" % (depends))
330 print(" cl->setWidgetType(ConfigList::File);")
331 elif format == 'dir':
332 print(" cl->setWidgetType(ConfigList::Dir);")
333 elif format == 'filedir':
334 print(" cl->setWidgetType(ConfigList::FileAndDir);")
335 elif type == 'obsolete':
336 print(" cfg->addObsolete(\"%s\");" % (name))
339 print(" cfg->addDisabled(\"%s\");" % (name))
343 def parseGroups(node):
344 name = node.getAttribute('name')
345 doc = node.getAttribute('docs')
346 print("%s%s" % (" //-----------------------------------------",
347 "----------------------------------"))
348 print(" cfg->addInfo(\"%s\",\"%s\");" % (name, doc))
349 print("%s%s" % (" //-----------------------------------------",
350 "----------------------------------"))
352 for n in node.childNodes:
353 if n.nodeType == Node.ELEMENT_NODE:
356 def parseGroupMap(node):
357 map = { 'bool':'bool', 'string':'QCString', 'enum':'QCString', 'int':'int', 'list':'QStrList' }
358 for n in node.childNodes:
359 if n.nodeType == Node.ELEMENT_NODE:
360 setting = n.getAttribute('setting')
362 print("#if %s" % (setting))
363 type = n.getAttribute('type')
364 name = n.getAttribute('id')
366 print(" %-8s %s;" % (map[type],name))
370 def parseGroupInit(node):
371 map = { 'bool':'Bool', 'string':'String', 'enum':'Enum', 'int':'Int', 'list':'List' }
372 for n in node.childNodes:
373 if n.nodeType == Node.ELEMENT_NODE:
374 setting = n.getAttribute('setting')
376 print("#if %s" % (setting))
377 type = n.getAttribute('type')
378 name = n.getAttribute('id')
380 print(" %-25s = ConfigImpl::instance()->get%s(__FILE__,__LINE__,\"%s\");" % (name,map[type],name))
384 def parseGroupMapInit(node):
385 map = { 'bool':'Bool', 'string':'String', 'enum':'String', 'int':'Int', 'list':'List' }
386 for n in node.childNodes:
387 if n.nodeType == Node.ELEMENT_NODE:
388 setting = n.getAttribute('setting')
390 print("#if %s" % (setting))
391 type = n.getAttribute('type')
392 name = n.getAttribute('id')
394 print(" m_map.insert(\"%s\",new Info%s(&ConfigValues::%s));" % (name,map[type],name))
398 def parseGroupCDocs(node):
399 for n in node.childNodes:
400 if n.nodeType == Node.ELEMENT_NODE:
401 type = n.getAttribute('type')
402 name = n.getAttribute('id')
404 if type != 'obsolete':
406 print(" \"%s\"," % (name))
410 if i != rng - 1: # since we go from 0 to rng-1
411 print(" \"%s\\n\"" % (line))
413 print(" \"%s\"" % (line))
416 def parseOptionDoc(node, first):
417 # Handling part for documentation
418 name = node.getAttribute('id')
419 type = node.getAttribute('type')
420 format = node.getAttribute('format')
421 defval = node.getAttribute('defval')
422 adefval = node.getAttribute('altdefval')
423 depends = node.getAttribute('depends')
424 setting = node.getAttribute('setting')
426 if (type != 'obsolete'):
427 for n in node.childNodes:
428 if (n.nodeName == "docs"):
429 if (n.getAttribute('documentation') != "0"):
430 if n.nodeType == Node.ELEMENT_NODE:
433 print(" \\anchor cfg_%s" % (name.lower()))
436 print("<dt>\\c %s <dd>" % (name))
438 print(" \\anchor cfg_%s" % (name.lower()))
439 print("<dt>\\c %s <dd>" % (name))
440 print(" \\addindex %s" % (name))
443 values = collectValues(node)
445 print("Possible values are: ")
450 print("%s and " % (val))
454 print("%s, " % (val))
458 print("The default value is: <code>%s</code>." % (defval))
460 elif (type == 'int'):
461 minval = node.getAttribute('minval')
462 maxval = node.getAttribute('maxval')
465 print("%s: %s%s%s, %s: %s%s%s, %s: %s%s%s." % (
466 " Minimum value", "<code>", minval, "</code>",
467 "maximum value", "<code>", maxval, "</code>",
468 "default value", "<code>", defval, "</code>"))
470 elif (type == 'bool'):
473 if (node.hasAttribute('altdefval')):
474 print("The default value is: system dependent.")
476 print("The default value is: <code>%s</code>." % (
477 "YES" if (defval == "1") else "NO"))
479 elif (type == 'list'):
480 if format == 'string':
481 values = collectValues(node)
486 print("%s and " % (val))
490 print("%s, " % (val))
492 elif (type == 'string'):
496 print("The default directory is: <code>%s</code>." % (
498 elif format == 'file':
499 abspath = node.getAttribute('abspath')
503 print("The default file is: <code>%s</code>." % (
506 print("%s: %s%s%s." % (
507 "The default file (with absolute path) is",
508 "<code>",defval,"</code>"))
512 print("The file has to be specified with full path.")
513 elif format =='image':
514 abspath = node.getAttribute('abspath')
518 print("The default image is: <code>%s</code>." % (
521 print("%s: %s%s%s." % (
522 "The default image (with absolute path) is",
523 "<code>",defval,"</code>"))
527 print("The image has to be specified with full path.")
528 else: # format == 'string':
531 print("The default value is: <code>%s</code>." % (
532 defval.replace('\\','\\\\')))
535 if (node.hasAttribute('depends')):
536 depends = node.getAttribute('depends')
538 print("%s \\ref cfg_%s \"%s\" is set to \\c YES." % (
539 "This tag requires that the tag", depends.lower(), depends.upper()))
543 def parseGroupsDoc(node):
544 name = node.getAttribute('name')
545 doc = node.getAttribute('docs')
546 print("\section config_%s %s" % (name.lower(), doc))
547 # Start of list has been moved to the first option for better
552 for n in node.childNodes:
553 if n.nodeType == Node.ELEMENT_NODE:
554 first = parseOptionDoc(n, first)
559 def parseGroupsList(node, commandsList):
561 for n in node.childNodes:
562 if n.nodeType == Node.ELEMENT_NODE:
563 type = n.getAttribute('type')
564 if type != 'obsolete':
565 commandsList = commandsList + (n.getAttribute('id'),)
571 for n in node.childNodes:
572 if n.nodeType == Node.TEXT_NODE:
573 doc += n.nodeValue.strip()
574 if n.nodeType == Node.CDATA_SECTION_NODE:
575 doc += n.nodeValue.rstrip("\r\n ").lstrip("\r\n")
580 def parseHeaderDoc(node):
582 for n in node.childNodes:
583 if n.nodeType == Node.ELEMENT_NODE:
584 if (n.nodeName == "docs"):
585 if (n.getAttribute('documentation') != "0"):
590 def parseFooterDoc(node):
592 for n in node.childNodes:
593 if n.nodeType == Node.ELEMENT_NODE:
594 if (n.nodeName == "docs"):
595 if (n.getAttribute('documentation') != "0"):
601 if len(sys.argv)<3 or (not sys.argv[1] in ['-doc','-cpp','-wiz','-maph','-maps']):
602 sys.exit('Usage: %s -doc|-cpp|-wiz|-maph|-maps config.xml' % sys.argv[0])
604 doc = xml.dom.minidom.parse(sys.argv[2])
605 except Exception as inst:
606 sys.stdout = sys.stderr
611 elem = doc.documentElement
612 if (sys.argv[1] == "-doc"):
613 print("/* WARNING: This file is generated!")
614 print(" * Do not edit this file, but edit config.xml instead and run")
615 print(" * python configgen.py -doc config.xml to regenerate this file!")
618 for n in elem.childNodes:
619 if n.nodeType == Node.ELEMENT_NODE:
620 if (n.nodeName == "header"):
622 # generate list with all commands
624 for n in elem.childNodes:
625 if n.nodeType == Node.ELEMENT_NODE:
626 if (n.nodeName == "group"):
627 commandsList = parseGroupsList(n, commandsList)
628 print("\\secreflist")
629 for x in sorted(commandsList):
630 print("\\refitem cfg_%s %s" % (x.lower(), x))
631 print("\\endsecreflist")
632 # process groups and options
633 for n in elem.childNodes:
634 if n.nodeType == Node.ELEMENT_NODE:
635 if (n.nodeName == "group"):
638 for n in elem.childNodes:
639 if n.nodeType == Node.ELEMENT_NODE:
640 if (n.nodeName == "footer"):
642 elif (sys.argv[1] == "-maph"):
643 print("/* WARNING: This file is generated!")
644 print(" * Do not edit this file, but edit config.xml instead and run")
645 print(" * python configgen.py -map config.xml to regenerate this file!")
647 print("#ifndef CONFIGVALUES_H")
648 print("#define CONFIGVALUES_H")
650 print("#include <qdict.h>")
651 print("#include <qstrlist.h>")
652 print("#include <qcstring.h>")
653 print("#include \"settings.h\"")
655 print("class ConfigValues")
658 print(" static ConfigValues &instance() { static ConfigValues theInstance; return theInstance; }")
659 for n in elem.childNodes:
660 if n.nodeType == Node.ELEMENT_NODE:
661 if (n.nodeName == "group"):
663 print(" void init();")
664 print(" struct Info")
666 print(" enum Type { Bool, Int, String, List, Unknown };")
667 print(" Info(Type t) : type(t) {}")
668 print(" virtual ~Info() {}")
671 print(" struct InfoBool : public Info")
673 print(" InfoBool(bool ConfigValues::*ptm) : Info(Info::Bool), item(ptm) {}")
674 print(" bool ConfigValues::*item;")
676 print(" struct InfoInt : public Info")
678 print(" InfoInt(int ConfigValues::*ptm) : Info(Info::Int), item(ptm) {}")
679 print(" int ConfigValues::*item;")
681 print(" struct InfoString : public Info")
683 print(" InfoString(QCString ConfigValues::*ptm) : Info(Info::String), item(ptm) {}")
684 print(" QCString ConfigValues::*item;")
686 print(" struct InfoList : public Info")
688 print(" InfoList(QStrList ConfigValues::*ptm) : Info(Info::List), item(ptm) {}")
689 print(" QStrList ConfigValues::*item;")
691 print(" const Info *get(const char *tag) const")
693 print(" return m_map.find(tag);")
696 print(" ConfigValues();")
697 print(" QDict<Info> m_map;")
701 elif (sys.argv[1] == "-maps"):
702 print("/* WARNING: This file is generated!")
703 print(" * Do not edit this file, but edit config.xml instead and run")
704 print(" * python configgen.py -maps config.xml to regenerate this file!")
706 print("#include \"configvalues.h\"")
707 print("#include \"configimpl.h\"")
709 print("ConfigValues::ConfigValues() : m_map(257)")
711 print(" m_map.setAutoDelete(TRUE);")
712 for n in elem.childNodes:
713 if n.nodeType == Node.ELEMENT_NODE:
714 if (n.nodeName == "group"):
718 print("void ConfigValues::init()")
720 for n in elem.childNodes:
721 if n.nodeType == Node.ELEMENT_NODE:
722 if (n.nodeName == "group"):
725 elif (sys.argv[1] == "-cpp"):
726 print("/* WARNING: This file is generated!")
727 print(" * Do not edit this file, but edit config.xml instead and run")
728 print(" * python configgen.py -cpp config.xml to regenerate this file!")
731 print("#include \"configoptions.h\"")
732 print("#include \"configimpl.h\"")
733 print("#include \"portable.h\"")
734 print("#include \"settings.h\"")
736 print("void addConfigOptions(ConfigImpl *cfg)")
738 print(" ConfigString *cs;")
739 print(" ConfigEnum *ce;")
740 print(" ConfigList *cl;")
741 print(" ConfigInt *ci;")
742 print(" ConfigBool *cb;")
745 for n in elem.childNodes:
746 if n.nodeType == Node.ELEMENT_NODE:
747 if (n.nodeName == "header"):
749 for n in elem.childNodes:
750 if n.nodeType == Node.ELEMENT_NODE:
751 if (n.nodeName == "group"):
754 elif (sys.argv[1] == "-wiz"):
755 print("/* WARNING: This file is generated!")
756 print(" * Do not edit this file, but edit config.xml instead and run")
757 print(" * python configgen.py -wiz config.xml to regenerate this file!")
759 print("#include \"configdoc.h\"")
760 print("#include \"docintf.h\"")
762 print("void addConfigDocs(DocIntf *doc)")
764 for n in elem.childNodes:
765 if n.nodeType == Node.ELEMENT_NODE:
766 if (n.nodeName == "header"):
768 for n in elem.childNodes:
769 if n.nodeType == Node.ELEMENT_NODE:
770 if (n.nodeName == "group"):
774 if __name__ == '__main__':