2 * Copyright (C) 2005-2013 Team XBMC
5 * This Program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2, or (at your option)
10 * This Program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with XBMC; see the file COPYING. If not, see
17 * <http://www.gnu.org/licenses/>.
21 import groovy.xml.XmlUtil
22 import org.apache.commons.lang.StringEscapeUtils
24 import groovy.text.SimpleTemplateEngine
25 import groovy.text.SimpleTemplateEngine
26 import java.util.regex.Pattern
29 * This class contains a series of helper methods for parsing a xbmc addon spec xml file. It is intended to be
30 * used from a bindings template.
37 private static List classes
38 private static Map outTypemap = [:]
39 private static def defaultOutTypeConversion = null
40 private static Map inTypemap = [:]
41 private static def defaultInTypeConversion = null
42 private static def doxygenXmlDir = null
43 public static String newline = System.getProperty("line.separator");
44 public static File curTemplateFile = null;
46 public static void setTempateFile(File templateFile) { curTemplateFile = templateFile }
49 * In order to use any of the typemap helper features, the Helper class needs to be initialized with
51 * @param pclasses is the list of all class nodes from the module
52 * @param poutTypemap is the typemap table for output return values to the scripting language
53 * @param defaultOutTypemap is the default typemap to use when the type conversion is unknown
54 * @param pinTypemap is the typemap table for input parameters from the scripting language
55 * @param defaultInTypemap is the default typemap for the input parameters from the scripting language
57 public static void setup(def template,List pclasses, Map poutTypemap, def defaultOutTypemap,
58 Map pinTypemap, def defaultInTypemap)
60 setTempateFile(template.binding.templateFile)
61 classes = pclasses ? pclasses : []
62 if (poutTypemap) outTypemap.putAll(poutTypemap)
63 if (defaultOutTypemap) defaultOutTypeConversion = defaultOutTypemap
64 if (pinTypemap) inTypemap.putAll(pinTypemap)
65 if (defaultInTypemap) defaultInTypeConversion = defaultInTypemap
68 public static class Sequence
72 public long increment() { return ++cur }
75 private static ThreadLocal<Sequence> curSequence = new ThreadLocal<Sequence>();
77 public static void setDoxygenXmlDir(File dir) { doxygenXmlDir = dir }
79 private static String retrieveDocStringFromDoxygen(Node methodOrClass)
81 if (doxygenXmlDir == null)
87 // make the class name or namespace
88 String doxygenId = findFullClassName(methodOrClass,'_1_1')
89 boolean isInClass = doxygenId != null
91 doxygenId = findNamespace(methodOrClass,'_1_1',false)
92 doxygenId = (isInClass ? 'class' : 'namespace') + doxygenId
94 String doxygenFilename = doxygenId + '.xml'
95 File doxygenXmlFile = new File(doxygenXmlDir,doxygenFilename)
96 if (! doxygenXmlFile.exists())
98 System.out.println("WARNING: Cannot find doxygen file for ${methodOrClass.toString()} which should be \"${doxygenXmlFile}\"")
102 Node docspec = (new XmlParser().parse(doxygenXmlFile))
103 if (methodOrClass.name() == 'class')
104 doc = docspec.compounddef[0].detaileddescription[0]
105 else // it's a method of some sort ... or it better be
107 Node memberdef = docspec.depthFirst().find {
108 return (it instanceof String) ? false :
109 ((it.name() == 'memberdef' && (it.@kind == 'function' || it.@kind == 'variable') && it.@id.startsWith(doxygenId)) &&
110 (it.name != null && it.name.text().trim() == methodOrClass.@sym_name))
113 doc = memberdef != null ? memberdef.detaileddescription[0] : null
124 if (it instanceof String)
128 if (it.name() == 'detaileddescription')
129 it.children().each handleDoc
130 else if (it.name() == 'para')
132 it.children().each handleDoc
133 ret += (it.parent()?.name() == 'listitem') ? newline : (newline + newline)
135 else if (it.name() == 'ref' || it.name() == "ulink")
136 ret += (it.text() + ' ')
137 else if (it.name() == 'verbatim')
138 ret += it.text().denormalize()
139 else if (it.name() == 'itemizedlist')
142 prevIndent = curIndent
144 it.children().each handleDoc
145 curIndent = prevIndent
147 else if (it.name() == 'listitem')
149 ret += (curIndent + '- ')
150 it.children().each handleDoc
152 else if (it.name() == 'linebreak')
154 else if (it.name() == 'ndash')
156 else if (it.name() == 'emphasis')
159 it.children().each handleDoc
162 System.out.println("WARNING: Cannot parse the following as part of the doxygen processing:" + XmlUtil.serialize(it))
166 doc.children().each handleDoc
169 return ret.denormalize()
173 * <p>This method uses the previously set outTypemap and defaultOutTypemap to produce the chunk of code
174 * that will be used to return the method invocation result to the scripting language. For example, in
175 * python, if the return type from the method is a long, then the OutConversion could look something like:</p>
177 * result = PyInt_FromLong(thingReturnedFromMethod);
179 * <p>This could have resulted from a mini-template stored as the way to handle 'long's in the outTypemap:</p>
181 * ${result} = PyInt_FromLong(${api});
183 * @param apiType - is the Swig typecode that describes the return type from the native method
184 * @param method - is the node from the module xml that contains the method description
185 * @return the code chunk as a string ready to be placed into the generated code.
187 public static String getOutConversion(String apiType, String apiName, Node method, Map overrideBindings = null, boolean recurse = true)
189 def convertTemplate = outTypemap[apiType]
191 // String apiLType = SwigTypeParser.convertTypeToLType(apiType)
192 // if (convertTemplate == null) convertTemplate = outTypemap[apiLType]
194 // is the returns a pointer to a known class
195 String className = null
196 if (convertTemplate == null && apiType.startsWith('p.'))
198 Node classNode = findClassNodeByName(parents(method)[0], SwigTypeParser.getRootType(apiType),method)
201 className = findFullClassName(classNode)
202 convertTemplate = defaultOutTypeConversion
206 if (convertTemplate == null)
208 // Look for Pattern for keys that might fit
209 convertTemplate = outTypemap.find({ key, value -> (key instanceof Pattern && key.matcher(apiType).matches()) })?.value
212 if (!convertTemplate)
214 String knownApiType = isKnownApiType(apiType,method)
217 convertTemplate = defaultOutTypeConversion
218 className = knownApiType
222 if (!convertTemplate)
224 // check the typedef resolution
225 String apiTypeResolved = SwigTypeParser.SwigType_resolve_all_typedefs(apiType)
226 if (!apiTypeResolved.equals(apiType))
227 return getOutConversion(apiTypeResolved, apiName, method, overrideBindings, recurse)
230 return getOutConversion(SwigTypeParser.SwigType_ltype(apiType),apiName,method,overrideBindings,false)
231 else if (!isKnownApiType(apiType,method))
232 throw new RuntimeException("WARNING: Cannot convert the return value of swig type ${apiType} for the call ${Helper.findFullClassName(method) + '::' + Helper.callingName(method)}")
235 boolean seqSetHere = false
236 Sequence seq = curSequence.get()
244 Map bindings = ['result' : apiName, 'api' : 'apiResult', 'type' : "${apiType}",
245 'method' : method, 'helper' : Helper.class,
246 'swigTypeParser' : SwigTypeParser.class,
248 if (className) bindings['classname'] = className
250 if (overrideBindings) bindings.putAll(overrideBindings)
252 if (convertTemplate instanceof List) /// then we expect the template string/file to be the first entry
254 Map additionalBindings = convertTemplate.size() > 1 ? convertTemplate[1] : [:]
255 bindings.putAll(additionalBindings)
256 convertTemplate = convertTemplate[0]
259 if (File.class.isAssignableFrom(convertTemplate.getClass()))
261 File cur = (File)convertTemplate
262 if (!cur.exists()) // see if the file is relative to the template file
264 File parent = curTemplateFile.getParentFile()
265 // find the relative path to the convertTemplate
266 File cwd = new File('.').getCanonicalFile()
267 String relative = cwd.getAbsoluteFile().toURI().relativize(convertTemplate.getAbsoluteFile().toURI()).getPath()
268 convertTemplate = new File(parent,relative)
270 // This is a fallback case which is hit occationally on OSX as a result
271 // of case mismatches between the two paths in the relativize call above.
272 if (!convertTemplate.exists())
273 convertTemplate = new File(parent,cur.toString())
277 if (seqSetHere) curSequence.set(null)
278 return new SimpleTemplateEngine().createTemplate(convertTemplate).make(bindings).toString()
282 * <p>This method uses the previously set inTypemap and defaultInTypemap to produce the chunk of code
283 * that will be used to convert input parameters from the scripting language to the native method invocation
284 * parameters. For example, if the native call takes a 'String' then the InConversion could look something like:</p>
286 * if (pythonStringArgument) PyXBMCGetUnicodeString(cArgument,pythonStringArgument,"cArgumentName");
288 * <p>This could have resulted from a mini-template stored as the way to handle 'long's in the outTypemap:</p>
290 * if (${slarg}) PyXBMCGetUnicodeString(${api},${slarg},"${api}");
292 * @param apiType - is the Swig typecode that describes the parameter type from the native method
293 * @param apiName - is the name of the parameter from the method parameter list in the api
294 * @param slName - is the name of the varialbe that holds the parameter from the scripting language
295 * @param method - is the node from the module xml that contains the method description
296 * @return the code chunk as a string ready to be placed into the generated code.
298 public static String getInConversion(String apiType, String apiName, String slName, Node method, Map overrideBindings = null)
300 return getInConversion(apiType, apiName, apiName, slName, method, overrideBindings);
304 * <p>This method uses the previously set inTypemap and defaultInTypemap to produce the chunk of code
305 * that will be used to convert input parameters from the scripting language to the native method invocation
306 * parameters. For example, if the native call takes a 'String' then the InConversion could look something like:</p>
308 * if (pythonStringArgument) PyXBMCGetUnicodeString(cArgument,pythonStringArgument,"cArgumentName");
310 * <p>This could have resulted from a mini-template stored as the way to handle 'long's in the outTypemap:</p>
312 * if (${slarg}) PyXBMCGetUnicodeString(${api},${slarg},"${api}");
314 * @param apiType - is the Swig typecode that describes the parameter type from the native method
315 * @param apiName - is the name of the varialbe that holds the api parameter
316 * @param paramName - is the name of the parameter from the method parameter list in the api
317 * @param slName - is the name of the varialbe that holds the parameter from the scripting language
318 * @param method - is the node from the module xml that contains the method description
319 * @return the code chunk as a string ready to be placed into the generated code.
321 public static String getInConversion(String apiType, String apiName, String paramName, String slName, Node method, Map overrideBindings = null)
323 def convertTemplate = inTypemap[apiType]
325 String apiLType = SwigTypeParser.convertTypeToLTypeForParam(apiType)
327 if (convertTemplate == null) convertTemplate = inTypemap[apiLType]
329 // is the returns a pointer to a known class
330 if (convertTemplate == null && apiType.startsWith('p.'))
332 // strip off rval qualifiers
333 String thisNamespace = Helper.findNamespace(method)
334 Node clazz = classes.find { Helper.findFullClassName(it) == apiLType.substring(2) ||
335 (it.@sym_name == apiLType.substring(2) && thisNamespace == Helper.findNamespace(it)) }
336 if (clazz != null) convertTemplate = defaultInTypeConversion
339 // Look for Pattern for keys that might fit
340 if (convertTemplate == null)
341 convertTemplate = inTypemap.find({ key, value -> (key instanceof Pattern && key.matcher(apiType).matches()) })?.value
344 if (convertTemplate == null)
345 convertTemplate = inTypemap.find({ key, value -> (key instanceof Pattern && key.matcher(apiLType).matches()) })?.value
347 if (!convertTemplate)
349 // check the typedef resolution
350 String apiTypeResolved = SwigTypeParser.SwigType_resolve_all_typedefs(apiType)
351 if (!apiTypeResolved.equals(apiType))
352 return getInConversion(apiTypeResolved, apiName, paramName, slName, method, overrideBindings)
354 // it's ok if this is a known type
355 if (!isKnownApiType(apiType,method) && !isKnownApiType(apiLType,method))
356 System.out.println("WARNING: Unknown parameter type: ${apiType} (or ${apiLType}) for the call ${Helper.findFullClassName(method) + '::' + Helper.callingName(method)}")
357 convertTemplate = defaultInTypeConversion
362 boolean seqSetHere = false
363 Sequence seq = curSequence.get()
372 'type': "${apiType}", 'ltype': "${apiLType}",
373 'slarg' : "${slName}", 'api' : "${apiName}",
374 'param' : "${paramName}",
375 'method' : method, 'helper' : Helper.class,
376 'swigTypeParser' : SwigTypeParser.class,
380 if (overrideBindings) bindings.putAll(overrideBindings)
382 if (convertTemplate instanceof List) /// then we expect the template string/file to be the first entry
384 Map additionalBindings = convertTemplate.size() > 1 ? convertTemplate[1] : [:]
385 bindings.putAll(additionalBindings)
386 convertTemplate = convertTemplate[0]
389 if (File.class.isAssignableFrom(convertTemplate.getClass()))
391 File cur = (File)convertTemplate
392 if (!cur.exists()) // see if the file is relative to the template file
394 File parent = curTemplateFile.getParentFile()
395 // find the relative path to the convertTemplate
396 File cwd = new File('.').getCanonicalFile()
397 String relative = cwd.getAbsoluteFile().toURI().relativize(convertTemplate.getAbsoluteFile().toURI()).getPath()
398 convertTemplate = new File(parent,relative)
400 // This is a fallback case which is hit occationally on OSX as a result
401 // of case mismatches between the two paths in the relativize call above.
402 if (!convertTemplate.exists())
403 convertTemplate = new File(parent,cur.toString())
407 if (seqSetHere) curSequence.set(null);
408 return new SimpleTemplateEngine().createTemplate(convertTemplate).make(bindings).toString()
414 static def ignoreAttributes = ['classes', 'symtab', 'sym_symtab',
415 'sym_overname', 'options', 'sym_nextSibling', 'csym_nextSibling',
416 'sym_previousSibling' ]
419 * <p>Transform a Swig generated xml file into something more manageable. For the most part this method will:</p>
421 * <li>1) Make all pertinent 'attributelist' elements actually be attributes of the parent element while
422 * an attribute with the name 'name' will become that parent element name.</li>
423 * <li>2) Filter out unused attributes</li>
424 * <li>3) Filter out the automatically included 'swig'swg'</li>
425 * <li>4) Flatten out the remaining 'include' elements</li>
426 * <li>5) Removes extraneous default argument function/method Node</li>
427 * <li>6) Converts all type tables to a single entry under the main module node removing all 1-1 mappings.</li>
428 * <li>7) Removes all non-public non-constructor methods.</li>
429 * @param swigxml is the raw swig output xml document
430 * @return the transformed document
432 public static Node transformSwigXml(Node swigxml)
434 Node node = transform(swigxml,
436 // do not include the 'include' entry that references the default swig.swg file.
437 !(it.name() == 'include' &&
438 // needs to also contain an attribute list with an attribute 'name' that matches the swig.swg file
440 it.name() == 'attributelist' && it.find {
441 it.name() == 'attribute' && it.@name == 'name' && it.@value =~ /swig\.swg$/
444 // also don't include any typescope entries
445 it.name() == 'typescopesitem' || it.name() == 'typetabsitem'
447 },{ key, value -> !ignoreAttributes.contains(key) })
449 // now make sure the outer most node is an include and there's only one
450 assert node.include?.size() == 1 && node.include[0].module?.size() == 1 && node.include[0].module[0]?.@name != null,
451 "Invalid xml doc result. Expected a single child node of the root node call 'include' with a single 'module' child node but got " + XmlUtil.serialize(node)
453 // create an outermost 'module' node with the correct name
454 Node ret = new Node(null, 'module', [ 'name':node.include[0].module[0].@name] )
455 node.include[0].children().each { if (it.name() != 'module') ret.append(it) }
457 // flatten out all other 'include' elements, parmlists, and typescopes
458 flatten(ret,['include', 'parmlist', 'typescope' ])
460 // remove any function nodes with default arguments
462 tmpl.addAll(ret.depthFirst())
463 for (Node cur : tmpl)
465 if ((cur.name() == 'function' || cur.name() == 'constructor') && cur.@defaultargs != null)
466 cur.parent().remove(cur)
469 // now validate that no other methods are overloaded since we can't handle those right now.
470 functionNodesByOverloads(ret).each { key, value -> assert value.size() == 1, "Cannot handle overloaded methods unless simply using defaulting: " + value }
472 // now gather up all of the typetabs and add a nice single
473 // typetab entry with a better format in the main module
474 List allTypetabs = ret.depthFirst().findAll { it.name() == 'typetab' }
475 Node typenode = new Node(ret,'typetab')
477 it.attributes().each { key, value ->
478 if (key != 'id' && key != value)
480 Node typeentry = new Node(null,'entry')
481 String namespace = findNamespace(it)
482 typeentry.@namespace = namespace != null ? namespace.trim() : ''
483 typeentry.@type = key
484 typeentry.@basetype = value
486 if (typenode.find({ it.@basetype == typeentry.@basetype && it.@namespace == typeentry.@namespace }) == null)
487 typenode.append(typeentry)
490 it.parent().remove(it)
493 // now remove all non-public methods, but leave constructors
494 List allMethods = ret.depthFirst().findAll({ it.name() == 'function' || it.name() == 'destructor' || it.name() == 'constructor'})
496 if (it.@access != null && it.@access != 'public' && it.name() != 'constructor')
497 it.parent().remove(it)
500 def doc = retrieveDocStringFromDoxygen(it)
501 if (doc != null && doc != '' && doc.trim() != ' ')
502 new Node(it,'doc',['value' : doc])
506 // now remove all non-public variables
507 List allVariables = ret.depthFirst().findAll({ it.name() == 'variable' })
509 if (it.@access != null && it.@access != 'public')
510 it.parent().remove(it)
513 def doc = retrieveDocStringFromDoxygen(it)
514 if (doc != null && doc != '' && doc.trim() != ' ')
515 new Node(it,'doc',['value' : doc])
519 // add the doc string to the classes
520 List allClasses = ret.depthFirst().findAll({ it.name() == 'class'})
522 def doc = retrieveDocStringFromDoxygen(it)
523 if (doc != null && doc != '' && doc.trim() != ' ')
524 new Node(it,'doc',['value' : doc])
531 * @return true if the class node has a defined constructor. false otherwise.
533 public static boolean hasDefinedConstructor(Node clazz)
535 return (clazz.constructor != null && clazz.constructor.size() > 0)
539 * @return true id this Node has a docstring associated with it.
541 public static boolean hasDoc(Node methodOrClass)
543 return methodOrClass.doc != null && methodOrClass.doc[0] != null && methodOrClass.doc[0].@value != null
547 * @return true of the class node has a constructor but it's hidden (not 'public'). false otherwise.
549 public static boolean hasHiddenConstructor(Node clazz)
551 return (hasDefinedConstructor(clazz) && clazz.constructor[0].@access != null && clazz.constructor[0].@access != 'public')
555 * <p>This will look through the entire module and look up a class node by name. It will return null if
556 * that class node isn't found. It's meant to be used to look up base classes from a base class list
557 * so it's fairly robust. It goes through the following rules:</p>
559 * <li>Does the FULL classname (considering the namespace) match the name provided.</li>
560 * <li>Does the FULL classname match the reference nodes namespace + '::' + the provided classname.</li>
561 * <li>Does the class node's name (which may contain the full classname) match the classname provided.</li>
563 * <p>Note, this method is not likely to find the classnode if you just pass a simple name and
564 * no referenceNode in the case where namespaces are used extensively.</p>
566 public static Node findClassNodeByName(Node module, String classname, Node referenceNode = null)
568 return module.depthFirst().findAll({ it.name() == 'class' }).find {
569 // first check to see if this FULL class name matches
570 if (findFullClassName(it).trim() == classname.trim()) return true
572 // now check to see if it matches the straight name considering the reference node
573 if (referenceNode != null && (findNamespace(referenceNode) + classname) == findFullClassName(it)) return true
575 // now just see if it matches the straight name
576 if (it.@name == classname) return true
583 * Find me the class node that this node either is, or is within.
584 * If this node is not within a class node then it will return null.
586 public static Node findClassNode(Node node)
588 if (node.name() == 'class') return node
589 return node.parent() == null ? null : findClassNode(node.parent())
593 * If this node is a class node, or a child of a class name (for example, a method) then
594 * the full classname, with the namespace will be returned. Otherwise, null.
596 public static String findFullClassName(Node node, String separator = '::')
599 List rents = parents(node, { it.name() == 'class' })
600 if (node.name() == 'class') rents.add(node)
605 ret += separator + it.@sym_name
608 return ret ? findNamespace(node,separator) + ret : null
612 * Given the Node this method looks to see if it occurs within namespace and returns
613 * the namespace as a String. It includes the trailing '::'
615 public static String findNamespace(Node node, String separator = '::', boolean endingSeparator = true)
618 parents(node, { it.name() == 'namespace' }).each {
622 ret += separator + it.@name
625 return ret == null ? '' : (ret + (endingSeparator ? separator : ''))
629 * Gather up all of the parent nodes in a list ordered from the top node, down to the
630 * node that's passed as a parameter.
632 public static List parents(Node node, Closure filter = null, List ret = null)
634 Node parent = node.parent()
637 ret = parents(parent,filter,ret)
638 if (filter == null || filter.call(parent))
641 else if (ret == null)
648 * Group together overloaded methods into a map keyed by the first method's id. Each
649 * entry in this map contains a list of nodes that represent overloaded versions of
652 public static Map functionNodesByOverloads(Node module)
654 // find function nodes
656 module.depthFirst().each {
657 if (it.name() == 'function' || it.name() == 'constructor' || it.name() == 'destructor')
659 String id = it.@sym_overloaded != null ? it.@sym_overloaded : it.@id
660 if (ret[id] == null) ret[id] = [it]
668 * Because the return type is a combination of the function 'decl' and the
669 * function 'type,' this method will construct a valid Swig typestring from
672 public static String getReturnSwigType(Node method)
674 // we're going to take a shortcut here because it appears only the pointer indicator
675 // ends up attached to the decl.
676 String prefix = (method.@decl != null && method.@decl.endsWith('.p.')) ? 'p.' : ''
677 return method.@type != null ? prefix + method.@type : 'void'
681 * Given the method node this will produce the actual name of the method as if
682 * it's being called. In the case of a constructor it will do a 'new.' In the
683 * case of a destructor it will produce a 'delete.'
685 public static String callingName(Node method)
687 // if we're not in a class we need the fully qualified name
688 String clazz = findFullClassName(method)
689 // if we're in a class then we are going to assume we have a 'self' pointer
690 // that we are going to invoke this on.
694 if (method.name() == 'constructor')
695 return "new ${findNamespace(method)}${method.@sym_name}"
697 if (method.name() == 'destructor')
700 // otherwise it's just a call on a class being invoked on an instance
705 * Swig has 'insert' nodes in it's parse tree that contain code chunks that are
706 * meant to be inserted into various positions in the generated code. This method
707 * will extract the nodes that refer to the specific section asked for. See the
708 * Swig documentation for more information.
710 public static List getInsertNodes(Node module, String section)
712 return module.insert.findAll { section == it.@section || (section == 'header' && it.@section == null) }
715 public static String unescape(Node insertSection) { return unescape(insertSection.@code) }
717 public static String unescape(String insertSection) { return StringEscapeUtils.unescapeHtml(insertSection) }
719 public static boolean isDirector(Node method)
721 Node clazz = findClassNode(method)
722 if (!clazz || !clazz.@feature_director)
724 if (method.name() == 'destructor')
726 if (method.name() == 'constructor')
728 return method.@storage && method.@storage == 'virtual'
732 * This method will search from the 'searchFrom' Node up to the root
733 * looking for a %feature("knownbasetypes") declaration that the given 'type' is
734 * known for 'searchFrom' Node.
736 public static boolean isKnownBaseType(String type, Node searchFrom)
738 return hasFeatureSetting(type,searchFrom,'feature_knownbasetypes',{ it.split(',').find({ it.trim() == type }) != null })
742 * This method will search from the 'searchFrom' Node up to the root
743 * looking for a %feature("knownapitypes") declaration that the given 'type' is
744 * known for 'searchFrom' Node.
746 public static String isKnownApiType(String type, Node searchFrom)
748 String rootType = SwigTypeParser.getRootType(type)
749 String namespace = findNamespace(searchFrom,'::',false)
750 String lastMatch = null
751 hasFeatureSetting(type,searchFrom,'feature_knownapitypes',{ it.split(',').find(
753 if (it.trim() == rootType)
758 // we assume the 'type' is defined within namespace and
759 // so we can walk up the namespace appending the type until
761 while (namespace != '')
763 // System.out.println('checking ' + (namespace + '::' + rootType))
764 if ((namespace + '::' + rootType) == it.trim())
766 lastMatch = it.trim()
769 // truncate the last namespace
770 int chop = namespace.lastIndexOf('::')
771 namespace = (chop > 0) ? namespace.substring(0,chop) : ''
778 private static String hasFeatureSetting(String type, Node searchFrom, String feature, Closure test)
783 Object attr = searchFrom.attribute(feature)
784 if (attr && test.call(attr))
785 return attr.toString()
787 return hasFeatureSetting(type,searchFrom.parent(),feature,test)
790 private static void flatten(Node node, List elementsToRemove)
792 for (boolean done = false; !done;)
795 for (Node child : node.breadthFirst())
797 if (elementsToRemove.contains(child.name()))
799 Node parent = child.parent()
801 child.each { parent.append(it) }
809 private static Node transform(Node node, Closure nodeFilter, Closure attributeFilter)
811 // need to create a map and a list of nodes (which will be children) from the
816 if (nodeFilter == null || nodeFilter.call(it) == true)
818 if (it.name() == 'attributelist')
820 Tuple results = transformSwigAttributeList(it)
821 attributes.putAll(results[0].findAll(attributeFilter))
822 List childNodes = results[1]
824 if (nodeFilter != null && nodeFilter.call(it) == true)
825 nodes.add(transform(it,nodeFilter,attributeFilter))
829 nodes.add(transform(it,nodeFilter,attributeFilter))
833 // transfer the addr attribute from the original node over to the 'id' attribute of the
834 // new node by adding it to the attributes map
837 // copy over the other attributes
838 node.attributes().findAll { key,value -> if (key != 'addr' && key != 'id') attributes[key] = value }
839 attributes['id'] = node.@addr
842 // In the case when the Node is a cdecl, we really want to replace the node name
843 // with the 'kind' attribute value.
845 if (node.name() == 'cdecl' && attributes.containsKey('kind'))
846 ret = new Node(null, attributes['kind'], attributes.findAll({key, value -> key != 'kind' } ))
848 ret = new Node(null, node.name(), attributes)
849 nodes.each { ret.append(it) }
853 private static Tuple transformSwigAttributeList(Node attributeList)
858 if (it.name() == 'attribute')
859 attributes[it.@name] = it.@value
863 return [attributes, nodes]