|
1 # Copyright (c) 2007-2010 Nokia Corporation and/or its subsidiary(-ies) All rights reserved. |
|
2 # This component and the accompanying materials are made available under the terms of the License |
|
3 # "Eclipse Public License v1.0" which accompanies this distribution, |
|
4 # and is available at the URL "http://www.eclipse.org/legal/epl-v10.html". |
|
5 # |
|
6 # Initial Contributors: |
|
7 # Nokia Corporation - initial contribution. |
|
8 # |
|
9 # Contributors: |
|
10 # |
|
11 # Description: |
|
12 # Checks links in DITA XML and reports issues. |
|
13 |
|
14 """ |
|
15 Created on 24 May 2010 |
|
16 |
|
17 @author: p2ross |
|
18 """ |
|
19 import os |
|
20 import sys |
|
21 import logging |
|
22 #import pprint |
|
23 #import fnmatch |
|
24 #import re |
|
25 import string |
|
26 import time |
|
27 from optparse import OptionParser#, check_choice |
|
28 try: |
|
29 from xml.etree import cElementTree as etree |
|
30 except ImportError: |
|
31 from xml.etree import ElementTree as etree |
|
32 #import multiprocessing |
|
33 |
|
34 __version__ = "0.1.0" |
|
35 |
|
36 |
|
37 class XsdBase(object): |
|
38 PREFIX = '{http://www.w3.org/2001/XMLSchema}' |
|
39 def _addSchemaPrefix(self, theTag): |
|
40 return '%s%s' % (self.PREFIX, theTag) |
|
41 |
|
42 def _tagSchemaMatches(self, theN, theTag): |
|
43 return theN.tag == self._addSchemaPrefix(theTag) |
|
44 |
|
45 def _tagSchemaMatchesList(self, theN, theTagS): |
|
46 for aTag in theTagS: |
|
47 if theN.tag == self._addSchemaPrefix(aTag): |
|
48 return True |
|
49 return False |
|
50 |
|
51 def _stripSchemaPrefix(self, theN): |
|
52 """Returns theNode.tag without the schema prefix.""" |
|
53 return theN.tag[len(self.PREFIX):] |
|
54 |
|
55 class XsdNodeBase(XsdBase): |
|
56 PREFIX = '{http://www.w3.org/2001/XMLSchema}' |
|
57 def __init__(self, theN): |
|
58 super(XsdNodeBase, self).__init__() |
|
59 self._name = theN.get('name') |
|
60 self._ref = theN.get('ref') |
|
61 # This will be prefix+element |
|
62 self._tag = theN.tag |
|
63 self._min = int(theN.get('minOccurs') or 1) |
|
64 if theN.get('maxOccurs') == 'unbounded': |
|
65 self._max = -1 |
|
66 else: |
|
67 self._max = int(theN.get('maxOccurs') or 1) |
|
68 logging.debug( |
|
69 'XsdNodeBase.__init__(): tag=%s, name=%s, ref=%s, min=%d, max=%d', |
|
70 self.tag, |
|
71 self._name, |
|
72 self._ref, |
|
73 self._min, |
|
74 self._max, |
|
75 ) |
|
76 |
|
77 @property |
|
78 def tag(self): |
|
79 return self._tag[len(self.PREFIX):] |
|
80 |
|
81 @property |
|
82 def attrRef(self): |
|
83 return self._ref |
|
84 |
|
85 @property |
|
86 def attrName(self): |
|
87 return self._name |
|
88 |
|
89 @property |
|
90 def isUnbounded(self): |
|
91 return self._max == -1 |
|
92 |
|
93 @property |
|
94 def minOccurs(self): |
|
95 return self._min |
|
96 |
|
97 @property |
|
98 def maxOccurs(self): |
|
99 return self._max |
|
100 |
|
101 @property |
|
102 def boundedString(self): |
|
103 """Returns a natural language description of bounds.""" |
|
104 if self._min == 1 and self._max == 1: |
|
105 return '' |
|
106 elif self._min == 0: |
|
107 if self._max == 1: |
|
108 # minOccurs="0" -> 'optional' |
|
109 return 'optional' |
|
110 elif self.isUnbounded: |
|
111 # minOccurs="0" maxOccurs="unbounded" -> 'any number' |
|
112 return 'any number' |
|
113 else: |
|
114 # minOccurs="0" maxOccurs="42" -> 'up to 42' |
|
115 return 'up to %d' % self._max |
|
116 elif self.isUnbounded: |
|
117 # minOccurs="17" maxOccurs="unbounded" -> 'at least 17' |
|
118 return 'at least %d' % self._min |
|
119 # minOccurs="17" maxOccurs="42" -> 'at least 17 and up to 42' |
|
120 return 'at least %d, and up to %d' % (self._min, self._max) |
|
121 |
|
122 class Attribute(XsdNodeBase): |
|
123 """Represents an element describing an attribute.""" |
|
124 def __init__(self, theN): |
|
125 super(Attribute, self).__init__(theN) |
|
126 assert(self._tagSchemaMatches(theN, 'attribute')) |
|
127 self._default = theN.get('default') |
|
128 self._use = theN.get('use') |
|
129 |
|
130 @property |
|
131 def default(self): |
|
132 return self._default |
|
133 |
|
134 @property |
|
135 def use(self): |
|
136 return self._use |
|
137 |
|
138 class RefBase(XsdNodeBase): |
|
139 """Represents a reference to a group or element""" |
|
140 def __init__(self, theN): |
|
141 super(RefBase, self).__init__(theN) |
|
142 assert(self._tagSchemaMatchesList(theN, ('group', 'element'))) |
|
143 assert(self.attrRef is not None) |
|
144 |
|
145 @property |
|
146 def isSequence(self): |
|
147 return False |
|
148 |
|
149 class RefSequence(XsdNodeBase): |
|
150 """Holds information on an xs:all, xs:choice or xs:sequence.""" |
|
151 def __init__(self, theN): |
|
152 super(RefSequence, self).__init__(theN) |
|
153 assert(self._tagSchemaMatchesList(theN, ('all', 'choice', 'sequence'))), 'Tag %s not in list' % theN.tag |
|
154 # List of class RefBase or, recursively, a class RefSequence |
|
155 self._refS = [] |
|
156 self._type = self.tag |
|
157 for aChild in theN.getchildren(): |
|
158 if not self._tagSchemaMatchesList(aChild, ('group', 'all', 'choice', 'sequence')): |
|
159 continue |
|
160 #self._type = self._stripSchemaPrefix(aChild) |
|
161 if self._tagSchemaMatches(aChild, 'group'): |
|
162 self._refS.append(RefBase(aChild)) |
|
163 elif self._tagSchemaMatchesList( |
|
164 aChild, |
|
165 ('choice', 'sequence', 'any',) |
|
166 ): |
|
167 self._refS.append(RefSequence(aChild)) |
|
168 |
|
169 def genRefs(self): |
|
170 """Generates the list of RefBase or RefSequence""" |
|
171 for aRef in self._refS: |
|
172 yield aRef |
|
173 |
|
174 @property |
|
175 def numRefs(self): |
|
176 return len(self._refS) |
|
177 |
|
178 @property |
|
179 def refs(self): |
|
180 return self._refS |
|
181 |
|
182 @property |
|
183 def isSequence(self): |
|
184 return True |
|
185 |
|
186 def qualifierString(self): |
|
187 if self._type == 'all': |
|
188 return '(any number, any order)' |
|
189 elif self._type == 'choice': |
|
190 return '(any number)' |
|
191 elif self._type == 'sequence': |
|
192 return '' |
|
193 return 'Unknown' |
|
194 |
|
195 def joinString(self): |
|
196 if self._type == 'all': |
|
197 return ' or ' |
|
198 elif self._type == 'choice': |
|
199 return ' or ' |
|
200 elif self._type == 'sequence': |
|
201 return ' then ' |
|
202 return '' |
|
203 |
|
204 @property |
|
205 def seqType(self): |
|
206 return self._type |
|
207 |
|
208 class DefBase(XsdNodeBase): |
|
209 """Represents a definition of a group, element or complexType""" |
|
210 def __init__(self, theN): |
|
211 super(DefBase, self).__init__(theN) |
|
212 assert(self._tagSchemaMatchesList(theN, ('group', 'element', 'complexType'))), 'Tag %s not in list' % theN.tag |
|
213 assert(self.attrName is not None or self._tagSchemaMatches(theN, 'complexType')) |
|
214 # List of class RefBase of class RefSequence |
|
215 self._refS = [] |
|
216 |
|
217 def genRefs(self): |
|
218 """Generates the list of RefBase""" |
|
219 for aRef in self._refS: |
|
220 yield aRef |
|
221 |
|
222 @property |
|
223 def numRefs(self): |
|
224 return len(self._refS) |
|
225 |
|
226 @property |
|
227 def refs(self): |
|
228 return self._refS |
|
229 |
|
230 class GroupDef(DefBase): |
|
231 """Represents the definition of a group i.e. <xs:group name="...">...</xs:group>. |
|
232 See: http://www.w3.org/TR/2004/REC-xmlschema-1-20041028/structures.html#element-group""" |
|
233 def __init__(self, theN): |
|
234 super(GroupDef, self).__init__(theN) |
|
235 assert(theN.tag == self._addSchemaPrefix('group')) |
|
236 # http://www.w3.org/TR/2004/REC-xmlschema-1-20041028/structures.html#element-group |
|
237 # Content: (annotation?, (all | choice | sequence)?) |
|
238 self._type = None |
|
239 for aChild in theN.getchildren(): |
|
240 if self._tagSchemaMatchesList(aChild, ('all', 'choice', 'sequence')): |
|
241 self._type = self._stripSchemaPrefix(aChild) |
|
242 else: |
|
243 # Unrecognised element or annotation |
|
244 continue |
|
245 if self._type is not None: |
|
246 # Read grand children and break |
|
247 for aGrandChild in aChild.getchildren(): |
|
248 if aGrandChild.get('ref') is not None \ |
|
249 and self._tagSchemaMatchesList( |
|
250 aGrandChild, |
|
251 ( |
|
252 'element', |
|
253 'group', |
|
254 #'choice', |
|
255 #'sequence', |
|
256 #'any', |
|
257 )): |
|
258 self._refS.append(RefBase(aGrandChild)) |
|
259 break |
|
260 |
|
261 def __str__(self): |
|
262 #print 'TRACE:', self._refS |
|
263 return self.joinString().join(['%s' % r.attrRef for r in self._refS]) \ |
|
264 + ' ' \ |
|
265 + self.qualifierString() |
|
266 |
|
267 def qualifierString(self): |
|
268 if self._type == 'all': |
|
269 return '(any number, any order)' |
|
270 elif self._type == 'choice': |
|
271 return '(any number)' |
|
272 elif self._type == 'sequence': |
|
273 return '' |
|
274 return 'Unknown' |
|
275 |
|
276 def joinString(self): |
|
277 if self._type == 'all': |
|
278 return ' or ' |
|
279 elif self._type == 'choice': |
|
280 return ' or ' |
|
281 elif self._type == 'sequence': |
|
282 return ' then ' |
|
283 return '' |
|
284 |
|
285 @property |
|
286 def groupType(self): |
|
287 return self._type |
|
288 |
|
289 class ComplexTypeDef(DefBase):#XsdNodeBase): |
|
290 """Represents the definition of a complexType definition i.e. <xs:complexType name="...">...</xs:group>. |
|
291 See: http://www.w3.org/TR/2004/REC-xmlschema-1-20041028/structures.html#element-complexType""" |
|
292 def __init__(self, theN): |
|
293 super(ComplexTypeDef, self).__init__(theN) |
|
294 assert(theN.tag == self._addSchemaPrefix('complexType')) |
|
295 # http://www.w3.org/TR/2004/REC-xmlschema-1-20041028/structures.html#element-complexType |
|
296 # Simplified content: ((group | all | choice | sequence)? |
|
297 self._type = None |
|
298 # Get xs:attribute |
|
299 self._attrS = [] |
|
300 for aChild in theN.getchildren(): |
|
301 # We only handle complexType |
|
302 if not self._tagSchemaMatchesList( |
|
303 aChild, |
|
304 ('group', 'all', 'choice', 'sequence', 'attribute')): |
|
305 # Unrecognised element or annotation |
|
306 continue |
|
307 self._type = self._stripSchemaPrefix(aChild) |
|
308 if self._tagSchemaMatches(aChild, 'group'): |
|
309 self._refS.append(RefBase(aChild)) |
|
310 elif self._tagSchemaMatchesList( |
|
311 aChild, |
|
312 ('choice', 'sequence', 'any',), |
|
313 ): |
|
314 self._refS.append(RefSequence(aChild)) |
|
315 elif self._tagSchemaMatches(aChild, 'attribute'): |
|
316 self._attrS.append(Attribute(aChild)) |
|
317 |
|
318 @property |
|
319 def groupType(self): |
|
320 return self._type |
|
321 |
|
322 def namedAttribute(self, theName): |
|
323 """Returns an Attribute object or None.""" |
|
324 for anA in self._attrS: |
|
325 if anA.attrName == theName: |
|
326 return anA |
|
327 |
|
328 class ElementDef(DefBase): |
|
329 """Represents the definition of a element definition i.e. <xs:element name="...">...</xs:group>. |
|
330 See: http://www.w3.org/TR/2004/REC-xmlschema-1-20041028/structures.html#element-group""" |
|
331 def __init__(self, theN): |
|
332 super(ElementDef, self).__init__(theN) |
|
333 assert(theN.tag == self._addSchemaPrefix('element')) |
|
334 # http://www.w3.org/TR/2004/REC-xmlschema-1-20041028/structures.html#element-element |
|
335 # Simplified content: (annotation?, ((simpleType | complexType)? |
|
336 self._type = None |
|
337 self._complexType = None |
|
338 for aChild in theN.getchildren(): |
|
339 # We only handle complexType |
|
340 if not self._tagSchemaMatchesList(aChild, ('complexType',)): |
|
341 # Unrecognised element or annotation |
|
342 continue |
|
343 # Process a <complexType> |
|
344 self._complexType = ComplexTypeDef(aChild) |
|
345 break |
|
346 |
|
347 @property |
|
348 def complexType(self): |
|
349 return self._complexType |
|
350 |
|
351 class GroupRef(RefBase): |
|
352 """Represents a reference of a group i.e. <xs:group ref="..." .../>. |
|
353 See: http://www.w3.org/TR/2004/REC-xmlschema-1-20041028/structures.html#element-group""" |
|
354 def __init__(self, theN): |
|
355 super(GroupRef, self).__init__(theN) |
|
356 assert(theN.tag == self._addSchemaPrefix('group')) |
|
357 |
|
358 class ElementRef(RefBase): |
|
359 """Represents a reference to an element i.e. <xs:element ref="..." .../>. |
|
360 See: http://www.w3.org/TR/2004/REC-xmlschema-1-20041028/structures.html#element-group""" |
|
361 def __init__(self, theN): |
|
362 super(ElementRef, self).__init__(theN) |
|
363 assert(theN.tag == self._addSchemaPrefix('element')) |
|
364 |
|
365 class XsdToPackageDef(XsdBase): |
|
366 """Create a PackageDef.xml file from a set of XSD files.""" |
|
367 DUMP_PREFIX = ' ' |
|
368 def __init__(self, thePrefix):#, theOutFile=None, theTitle=''): |
|
369 super(XsdToPackageDef, self).__init__() |
|
370 # Maps of group definitions and references |
|
371 self._groupDefMap = {} |
|
372 self._groupRefMap = {} |
|
373 # Maps of element definitions and references |
|
374 self._elemDefMap = {} |
|
375 self._elemRefMap = {} |
|
376 # Top level complex types |
|
377 self._complexTypeMap = {} |
|
378 # Element prefix |
|
379 self._prefix = thePrefix |
|
380 |
|
381 ########################## |
|
382 # Section: Add an XSD file |
|
383 ########################## |
|
384 def addXsdFile(self, thePath): |
|
385 """Read an XSD and add it to the representation.""" |
|
386 t = etree.parse(thePath) |
|
387 r = t.getroot() |
|
388 self._addGroups(r) |
|
389 self._addElements(r) |
|
390 self._addComplexTypes(r) |
|
391 |
|
392 def addXsdPath(self, thePath): |
|
393 if os.path.isfile(thePath): |
|
394 if os.path.splitext(thePath)[1].lower() == '.xsd': |
|
395 self.addXsdFile(thePath) |
|
396 elif os.path.isdir(thePath): |
|
397 for aName in os.listdir(thePath): |
|
398 self.addXsdPath(os.path.join(thePath, aName)) |
|
399 |
|
400 def _addGroups(self, root): |
|
401 """Adds to the group maps.""" |
|
402 for anE in root.getchildren(): |
|
403 if self._tagSchemaMatches(anE, 'group'): |
|
404 if anE.get('name') is not None: |
|
405 self._groupDefMap[anE.get('name')] = GroupDef(anE) |
|
406 elif anE.get('ref') is not None: |
|
407 self._groupRefMap[anE.get('ref')] = GroupRef(anE) |
|
408 |
|
409 def _addElements(self, root): |
|
410 """Adds to the element maps.""" |
|
411 for anE in root.getchildren(): |
|
412 if self._tagSchemaMatches(anE, 'element'): |
|
413 if anE.get('name') is not None: |
|
414 self._elemDefMap[anE.get('name')] = ElementDef(anE) |
|
415 elif anE.get('ref') is not None: |
|
416 self._elemRefMap[anE.get('ref')] = ElementRef(anE) |
|
417 |
|
418 def _addComplexTypes(self, root): |
|
419 for anE in root.getchildren(): |
|
420 if self._tagSchemaMatches(anE, 'complexType'): |
|
421 self._complexTypeMap[anE.get('name')] = ComplexTypeDef(anE) |
|
422 ########################## |
|
423 # End: Add an XSD file |
|
424 ########################## |
|
425 |
|
426 ########################## |
|
427 # Section: Query the IR |
|
428 ########################## |
|
429 def elemContains(self, theElemName): |
|
430 """Returns a list of immediate child elements that this element contains.""" |
|
431 logging.debug('elemContains(%s)' % theElemName) |
|
432 retVal = [] |
|
433 if not self._elemDefMap.has_key(theElemName): |
|
434 return retVal |
|
435 myElem = self._elemDefMap[theElemName] |
|
436 if myElem.complexType is not None: |
|
437 logging.debug('elemContains(): complexType: %s', myElem.complexType) |
|
438 #print 'myElem', myElem |
|
439 for aRef in myElem.complexType.genRefs(): |
|
440 logging.debug('elemContains(): complexType tag: %s, attrRef: %s', |
|
441 aRef.tag, |
|
442 aRef.attrRef, |
|
443 ) |
|
444 self._addFromComplexTypeSequence(aRef, retVal) |
|
445 # myRef = aRef.attrRef |
|
446 # #print 'myRef', myRef |
|
447 # #if self._elemDefMap.has_key(myRef): |
|
448 # # retVal.append(myRef) |
|
449 # if self._groupDefMap.has_key(myRef): |
|
450 # self._addContainsGroup(myRef, retVal) |
|
451 # else: |
|
452 # retVal.append(myRef) |
|
453 logging.debug('elemContains(%s) returns %s', theElemName, str(retVal)) |
|
454 return retVal |
|
455 |
|
456 def _addFromComplexTypeSequence(self, theRefObj, theList): |
|
457 """Recursively add containing elements.""" |
|
458 myRef = theRefObj.attrRef |
|
459 logging.debug('_addFromComplexTypeSequence(%s):', myRef) |
|
460 if theRefObj.isSequence: |
|
461 for aSubRef in theRefObj.refs: |
|
462 logging.debug('_addFromComplexTypeSequence(): recursing on: %s', |
|
463 myRef) |
|
464 self._addFromComplexTypeSequence(aSubRef, theList) |
|
465 elif myRef is not None: |
|
466 if self._groupDefMap.has_key(myRef): |
|
467 self._addContainsGroup(myRef, theList) |
|
468 else: |
|
469 logging.debug('_addFromComplexTypeSequence(): adding: %s', |
|
470 myRef) |
|
471 theList.append(myRef) |
|
472 |
|
473 def _addContainsGroup(self, theRef, theList): |
|
474 logging.debug('_addContainsGroup(%s)' % theRef) |
|
475 assert(self._groupDefMap.has_key(theRef)) |
|
476 for aRef in self._groupDefMap[theRef].genRefs(): |
|
477 #print 'TRACE: aRef', aRef.tag |
|
478 logging.debug('_addContainsGroup(): aRef.tag: %s aRef.attrRef: %s' \ |
|
479 % (aRef.tag, aRef.attrRef)) |
|
480 if aRef.tag == 'element': |
|
481 theList.append(aRef.attrRef) |
|
482 elif aRef.tag == 'group' and self._groupDefMap.has_key(aRef.attrRef): |
|
483 self._addContainsGroup(aRef.attrRef, theList) |
|
484 |
|
485 ########################## |
|
486 # End: Query the IR |
|
487 ########################## |
|
488 |
|
489 ########################## |
|
490 # Section: Debug/trace |
|
491 ########################## |
|
492 def dump(self, s=sys.stdout): |
|
493 s.write(' Group definitions '.center(75, '-')+'\n') |
|
494 keyS = self._groupDefMap.keys() |
|
495 keyS.sort() |
|
496 for k in keyS: |
|
497 s.write('%40s : %s\n' % (k, self._groupDefMap[k])) |
|
498 s.write(' EOL '.center(75, '-')+'\n\n') |
|
499 s.write(' Group references '.center(75, '-')+'\n') |
|
500 keyS = self._groupRefMap.keys() |
|
501 keyS.sort() |
|
502 for k in keyS: |
|
503 s.write('%40s : %s\n' % (k, self._groupRefMap[k])) |
|
504 s.write(' EOL '.center(75, '-')+'\n\n') |
|
505 s.write(' Element definitions '.center(75, '-')+'\n') |
|
506 keyS = self._elemDefMap.keys() |
|
507 keyS.sort() |
|
508 for k in keyS: |
|
509 s.write('%40s : %s\n' % (k, self._elemDefMap[k])) |
|
510 s.write(' EOL '.center(75, '-')+'\n\n') |
|
511 s.write(' Element references '.center(75, '-')+'\n') |
|
512 keyS = self._elemRefMap.keys() |
|
513 keyS.sort() |
|
514 for k in keyS: |
|
515 s.write('%40s : %s\n' % (k, self._elemRefMap[k])) |
|
516 s.write(' EOL '.center(75, '-')+'\n\n') |
|
517 s.write(' Complex types '.center(75, '-')+'\n') |
|
518 keyS = self._complexTypeMap.keys() |
|
519 keyS.sort() |
|
520 for k in keyS: |
|
521 s.write('%40s : %s\n' % (k, self._complexTypeMap[k])) |
|
522 s.write(' EOL '.center(75, '-')+'\n\n') |
|
523 s.write(' Parent to child relationship '.center(75, '-')+'\n') |
|
524 # A map {element_name : [parent element, ...], ...} |
|
525 myPcMap = self._retContainedByMap() |
|
526 keyS = myPcMap.keys() |
|
527 keyS.sort() |
|
528 for k in keyS: |
|
529 s.write('Child: %s\n' % k) |
|
530 s.write(' Contained by: %s\n' % myPcMap[k]) |
|
531 s.write(' EOL '.center(75, '-')+'\n\n') |
|
532 # Run through the elements |
|
533 keyS = self._elemDefMap.keys() |
|
534 keyS.sort() |
|
535 for k in keyS: |
|
536 myElem = self._elemDefMap[k] |
|
537 s.write('Element: %s\n' % k) |
|
538 if myElem.complexType is not None: |
|
539 for aRef in myElem.complexType.genRefs(): |
|
540 #print 'TRACE: aRef', aRef |
|
541 if aRef.isSequence: |
|
542 self._dumpSequenceRef(s, aRef, level=1) |
|
543 else: |
|
544 self._dumpRef(s, aRef, level=1) |
|
545 else: |
|
546 s.write(' No complexType\n') |
|
547 s.write('\n') |
|
548 |
|
549 def _dumpRef(self, s, theRef, level=1): |
|
550 assert(not theRef.isSequence) |
|
551 if self._groupDefMap.has_key(theRef.attrRef): |
|
552 s.write('%s%s %s [%s]\n' \ |
|
553 % (self.DUMP_PREFIX*level, |
|
554 theRef.attrRef, |
|
555 '(group)', |
|
556 theRef.boundedString, |
|
557 #self._groupDefMap[aRef.attrRef].boundedString, |
|
558 ) |
|
559 ) |
|
560 self._dumpGroupRef(s, theRef.attrRef, level) |
|
561 #for aGm in self._groupDefMap[aRef.attrRef].genRefs(): |
|
562 # print ' <%s> %s %s %s' \ |
|
563 # % (aGm.tag, aGm.attrName, aGm.attrRef, aGm.boundedString) |
|
564 elif self._elemDefMap.has_key(theRef.attrRef): |
|
565 s.write('%s%s %s\n' % (self.DUMP_PREFIX*level, theRef.attrRef, '(element)')) |
|
566 elif self._complexTypeMap.has_key(theRef.attrRef): |
|
567 s.write('%s%s %s\n' % (self.DUMP_PREFIX*level, theRef.attrRef, '(complex)')) |
|
568 else: |
|
569 s.write('%s%s %s\n' % (self.DUMP_PREFIX*level, theRef.attrRef, '(not in my IR)')) |
|
570 |
|
571 def _dumpGroupRef(self, s, theR, level=1): |
|
572 """Dump out references to groups, recursively using self._groupDefMap.""" |
|
573 assert(self._groupDefMap.has_key(theR)) |
|
574 if self._groupDefMap[theR].numRefs == 1 \ |
|
575 and self._groupDefMap[theR].refs[0].tag == 'element': |
|
576 s.write('%sELEMENT<%s> %s %s "%s" [%s]\n' \ |
|
577 % ( |
|
578 self.DUMP_PREFIX*level, |
|
579 self._groupDefMap[theR].refs[0].tag, |
|
580 self._groupDefMap[theR].refs[0].attrName, |
|
581 self._groupDefMap[theR].refs[0].attrRef, |
|
582 self._groupDefMap[theR].refs[0], |
|
583 self._groupDefMap[theR].refs[0].boundedString, |
|
584 ) |
|
585 ) |
|
586 else: |
|
587 s.write('%sGROUP <%s>%s\n' \ |
|
588 % (self.DUMP_PREFIX*level, self._groupDefMap[theR].tag, self._groupDefMap[theR])) |
|
589 for aGm in self._groupDefMap[theR].genRefs(): |
|
590 #print '%s<%s> %s %s "%s"' \ |
|
591 # % (self.DUMP_PREFIX*level, aGm.tag, aGm.attrName, aGm.attrRef, aGm)#aGm.boundedString) |
|
592 if aGm.tag == 'group' \ |
|
593 and aGm.attrRef \ |
|
594 and self._groupDefMap.has_key(aGm.attrRef): |
|
595 self._dumpGroupRef(s, aGm.attrRef, level+1) |
|
596 elif aGm.tag == 'element': |
|
597 s.write('%sCHLDELE<%s> %s %s "%s"\n' \ |
|
598 % (self.DUMP_PREFIX*level, aGm.tag, aGm.attrName, aGm.attrRef, aGm))#aGm.boundedString) |
|
599 |
|
600 def _dumpSequenceRef(self, s, theR, level=1): |
|
601 """Dump out sequences recursively.""" |
|
602 assert(theR.isSequence) |
|
603 for i, aRef in enumerate(theR.genRefs()): |
|
604 if i > 0: |
|
605 s.write('%s[%s]\n' % (self.DUMP_PREFIX*level, theR.joinString())) |
|
606 #print '%sTRACE: _dumpSequenceRef aRef: %s' % (self.DUMP_PREFIX*level, aRef) |
|
607 if aRef.isSequence: |
|
608 self._dumpSequenceRef(s, aRef, level=level+1) |
|
609 else: |
|
610 self._dumpRef(s, aRef, level=level+1) |
|
611 s.write('%s[%s]\n' % (self.DUMP_PREFIX*level, theR.qualifierString())) |
|
612 ########################## |
|
613 # End: Debug/trace |
|
614 ########################## |
|
615 |
|
616 ########################## |
|
617 # Section: Output |
|
618 ########################## |
|
619 def _writeHeader(self, theS, theTitle): |
|
620 self._writeLine(theS, '<?xml version="1.0" encoding="UTF-8"?>') |
|
621 self._writeLine(theS, """<!DOCTYPE reference PUBLIC "-//OASIS//DTD DITA Reference//EN" "../../dtd/reference.dtd">""") |
|
622 self._writeLine(theS, """<reference id="%sapiref" xml:lang="en-us">""" % self._prefix) |
|
623 self._writeLine(theS, '<title>%s</title>' % theTitle) |
|
624 |
|
625 def _writeLine(self, theS, theL): |
|
626 theS.write(theL) |
|
627 theS.write('\n') |
|
628 |
|
629 def _write(self, theS, theStr): |
|
630 theS.write(theStr) |
|
631 |
|
632 def close(self, theS): |
|
633 self._writeLine(theS, '</reference>') |
|
634 theS.close() |
|
635 |
|
636 def _retContainedByMap(self): |
|
637 """Returns a map {element_name : [parent element, ...], ...}.""" |
|
638 retMap = {} |
|
639 keyS = self._elemDefMap.keys() |
|
640 # Ensure that every element is represented |
|
641 for myName in keyS: |
|
642 retMap[myName] = [] |
|
643 for myName in keyS: |
|
644 myContainS = self.elemContains(myName) |
|
645 for aChild in myContainS: |
|
646 try: |
|
647 retMap[aChild].append(myName) |
|
648 except KeyError: |
|
649 retMap[aChild] = [myName] |
|
650 return retMap |
|
651 |
|
652 def _retDirName(self, theName): |
|
653 assert(theName.startswith(self._prefix)) |
|
654 # Slice the name to get the directory |
|
655 # e.g. 'cxxVariableDeclarationFile' becomes 'cxxVariable' |
|
656 # Special case where all ...Ref.dita are in cxxAPIMap/ |
|
657 if theName.endswith('Ref') \ |
|
658 or theName == 'cxxAPIMap': |
|
659 return 'cxxAPIMap' |
|
660 i = len(self._prefix) + 1 |
|
661 while i < len(theName) and theName[i] in string.lowercase: |
|
662 i += 1 |
|
663 retVal = theName[:i] |
|
664 # This is a bit clunky for these special cases |
|
665 if retVal in ('cxxEnumerator', 'cxxEnumerators'): |
|
666 retVal = 'cxxEnumeration' |
|
667 return retVal |
|
668 |
|
669 def _writeElementNameToFile(self, theS, theName): |
|
670 if self._elemDefMap.has_key(theName) \ |
|
671 and theName.startswith(self._prefix): |
|
672 # Slice the name to get the directory |
|
673 theDir = self._retDirName(theName) |
|
674 self._writeLine(theS, '<xref href="%s/%s.dita">%s</xref> ' \ |
|
675 % (theDir, theName, theName) |
|
676 ) |
|
677 else: |
|
678 # Write out as a keyword |
|
679 self._writeLine(theS, '<keyword>%s</keyword>, ' % theName) |
|
680 |
|
681 def _writeContentModelToStream(self, theS, theName): |
|
682 assert(self._elemDefMap.has_key(theName)) |
|
683 hasWritten = False |
|
684 myElem = self._elemDefMap[theName] |
|
685 if myElem.complexType is not None: |
|
686 for aRef in myElem.complexType.genRefs(): |
|
687 #print 'TRACE: aRef', aRef |
|
688 if aRef.isSequence: |
|
689 if self._writeContentModelSequenceRef(theS, aRef): |
|
690 hasWritten = True |
|
691 else: |
|
692 if self._writeContentModelRef(theS, aRef): |
|
693 hasWritten = True |
|
694 return hasWritten |
|
695 |
|
696 def _writeContentModelSequenceRef(self, theS, theR): |
|
697 assert(theR.isSequence) |
|
698 hasWritten = False |
|
699 if theR.numRefs > 0: |
|
700 self._write(theS, '(') |
|
701 for i, aRef in enumerate(theR.genRefs()): |
|
702 if i > 0: |
|
703 self._writeLine(theS, ' %s ' % theR.joinString()) |
|
704 hasWritten = True |
|
705 if aRef.isSequence: |
|
706 if self._writeContentModelSequenceRef(theS, aRef): |
|
707 hasWritten = True |
|
708 else: |
|
709 if self._writeContentModelRef(theS, aRef): |
|
710 hasWritten = True |
|
711 if theR.qualifierString(): |
|
712 self._writeLine(theS, '<i>%s</i>' % theR.qualifierString()) |
|
713 if theR.numRefs > 0: |
|
714 self._write(theS, ')') |
|
715 return hasWritten |
|
716 |
|
717 def _writeContentModelRef(self, theS, theR): |
|
718 assert(not theR.isSequence) |
|
719 hasWritten = False |
|
720 if self._groupDefMap.has_key(theR.attrRef): |
|
721 #s.write('%s %s\n' % (theR.attrRef, '(group)')) |
|
722 self._writeContentModelGroupRefToStream(theS, theR)#.attrRef) |
|
723 hasWritten = True |
|
724 elif self._elemDefMap.has_key(theR.attrRef): |
|
725 pass#s.write('%s %s\n' % (theR.attrRef, '(element)')) |
|
726 elif self._complexTypeMap.has_key(theR.attrRef): |
|
727 pass#s.write('%s %s\n' % (theR.attrRef, '(complex)')) |
|
728 else: |
|
729 #s.write('%s %s\n' % (theR.attrRef, '(not in my IR)')) |
|
730 self._writeLine(theS, '<keyword>%s</keyword>, ' % theR.attrRef) |
|
731 hasWritten = True |
|
732 return hasWritten |
|
733 |
|
734 def _writeContentModelGroupRefToStream(self, theS, theR): |
|
735 assert(self._groupDefMap.has_key(theR.attrRef)) |
|
736 myGroup = self._groupDefMap[theR.attrRef] |
|
737 if myGroup.numRefs == 1 \ |
|
738 and myGroup.refs[0].tag == 'element': |
|
739 # s.write('%sELEMENT<%s> %s %s "%s"\n' \ |
|
740 # % ( |
|
741 # self.DUMP_PREFIX*level, |
|
742 # self._groupDefMap[theR.attrRef].refs[0].tag, |
|
743 # self._groupDefMap[theR.attrRef].refs[0].attrName, |
|
744 # self._groupDefMap[theR.attrRef].refs[0].attrRef, |
|
745 # self._groupDefMap[theR.attrRef].refs[0], |
|
746 # ) |
|
747 # ) |
|
748 #self._write(theS, '(') |
|
749 aN = myGroup.refs[0].attrRef |
|
750 self._writeElementNameToFile(theS, aN) |
|
751 #self._writeLine(theS, '<xref href="%s/%s.dita">%s</xref>, %s' \ |
|
752 # % (aN, aN, aN, theR.boundedString)) |
|
753 if theR.boundedString: |
|
754 self._writeLine(theS, '(<i>%s</i>)' % theR.boundedString) |
|
755 #self._write(theS, ')') |
|
756 else: |
|
757 #s.write('%sGROUP <%s>%s\n' \ |
|
758 # % (self.DUMP_PREFIX*level, self._groupDefMap[theR.attrRef].tag, self._groupDefMap[theR.attrRef])) |
|
759 if myGroup.numRefs > 0: |
|
760 self._write(theS, '(') |
|
761 for i, aGm in enumerate(myGroup.genRefs()): |
|
762 #print '%s<%s> %s %s "%s"' \ |
|
763 # % (self.DUMP_PREFIX*level, aGm.tag, aGm.attrName, aGm.attrRef, aGm)#aGm.boundedString) |
|
764 if i > 0: |
|
765 self._writeLine(theS, myGroup.joinString()) |
|
766 if aGm.tag == 'group' \ |
|
767 and aGm.attrRef \ |
|
768 and self._groupDefMap.has_key(aGm.attrRef): |
|
769 self._writeContentModelGroupRefToStream(theS, aGm)#.attrRef) |
|
770 elif aGm.tag == 'element' \ |
|
771 and aGm.attrRef: |
|
772 self._writeElementNameToFile(theS, aGm.attrRef) |
|
773 #if self._elemDefMap.has_key(aGm.attrRef): |
|
774 # aN = aGm.attrRef |
|
775 # self._writeLine(theS, '<xref href="%s/%s.dita">%s</xref>, ' % (aN, aN, aN)) |
|
776 #else: |
|
777 # self._writeLine(theS, '<keyword>%s</keyword>, ' % aGm.attrRef) |
|
778 #elif aGm.tag == 'element': |
|
779 # s.write('%sCHLDELE<%s> %s %s "%s"\n' \ |
|
780 # % (self.DUMP_PREFIX*level, aGm.tag, aGm.attrName, aGm.attrRef, aGm))#aGm.boundedString) |
|
781 self._writeLine(theS, self._groupDefMap[theR.attrRef].qualifierString()) |
|
782 if myGroup.numRefs > 0: |
|
783 self._write(theS, ')') |
|
784 |
|
785 def writeToFile(self, thePath, theTitle): |
|
786 myS = open(thePath, 'w') |
|
787 self._writeHeader(myS, theTitle) |
|
788 #self.dump() |
|
789 myChildParentMap = self._retContainedByMap() |
|
790 #print 'TRACE: myChildParentMap', myChildParentMap |
|
791 # Run through the elements |
|
792 keyS = self._elemDefMap.keys() |
|
793 keyS.sort() |
|
794 for myName in keyS: |
|
795 myElem = self._elemDefMap[myName] |
|
796 logging.info('Writing element: <%s>' % myName) |
|
797 self._writeLine(myS, '<reference id="%s-reference" xml:lang="en-us">' % myName) |
|
798 self._writeLine(myS, '<title>Element: %s</title>' % myName) |
|
799 self._writeLine(myS, '<refbody>') |
|
800 # Section: Contained by |
|
801 self._writeLine(myS, '<section id="%s-containedBy-section" outputclass="elementContainedBy">' % myName) |
|
802 self._writeLine(myS, '<title>Contained by</title>') |
|
803 self._writeLine(myS, '<p id="%s-containedBy-p">' % myName) |
|
804 for aParent in myChildParentMap[myName]: |
|
805 self._writeElementNameToFile(myS, aParent) |
|
806 #if self._elemDefMap.has_key(aParent): |
|
807 # self._writeLine(myS, '<xref href="%s/%s.dita">%s</xref>, ' \ |
|
808 # % (aParent, aParent, aParent)) |
|
809 #else: |
|
810 # # Write out as a keyword |
|
811 # self._writeLine(myS, '<keyword>%s</keyword>, ' % aParent) |
|
812 self._writeLine(myS, '</p>') |
|
813 self._writeLine(myS, '</section>') |
|
814 # Section: Contains |
|
815 self._writeLine(myS, |
|
816 '<section id="%s-contains-section" outputclass="elementContains">' % myName) |
|
817 self._writeLine(myS, '<title>Contains</title>') |
|
818 self._writeLine(myS, '<p id="%s-contains-p">' % myName) |
|
819 myContainS = self.elemContains(myName) |
|
820 myContainS.sort() |
|
821 #print 'myContainS', myContainS |
|
822 for aN in myContainS: |
|
823 self._writeElementNameToFile(myS, aN) |
|
824 self._writeLine(myS, '</p>') |
|
825 self._writeLine(myS, '</section>') |
|
826 # Section: Content Model |
|
827 self._writeLine(myS, '<section id="%s-contentModel-section" outputclass="elementContentModel">' % myName) |
|
828 self._writeLine(myS, '<title>Content Model</title>') |
|
829 self._writeLine(myS, '<p id="%s-contentModel-p">' % myName) |
|
830 # Content model contents |
|
831 if not self._writeContentModelToStream(myS, myName): |
|
832 self._writeLine(myS, 'No content.') |
|
833 self._writeLine(myS, '</p>') |
|
834 self._writeLine(myS, '</section>') |
|
835 # Section: Attributes - empty |
|
836 self._writeLine(myS, '<section id="%s-attList-section" outputclass="elementAttList" />' % myName) |
|
837 # Section: classValue |
|
838 self._writeLine(myS, '<section id="%s-classValue-section" outputclass="elementClassValue">' % myName) |
|
839 self._writeLine(myS, '<title>Inheritance</title>') |
|
840 self._writeLine(myS, '<p id="%s-classValue-p">' % myName) |
|
841 if myElem.complexType is not None: |
|
842 myClassAttr = myElem.complexType.namedAttribute('class') |
|
843 if myClassAttr is not None \ |
|
844 and myClassAttr.default is not None: |
|
845 #print 'TRACE: myClassAttr.default: "%s"' % myClassAttr.default |
|
846 for aStr in myClassAttr.default[1:].strip().split(): |
|
847 if aStr.find('/') != -1: |
|
848 a,b = aStr.split('/') |
|
849 myS.write(' %s/' % a) |
|
850 myS.write('<keyword>%s</keyword>' % b) |
|
851 self._writeLine(myS, '</p>') |
|
852 self._writeLine(myS, '</section>') |
|
853 self._writeLine(myS, '</refbody>') |
|
854 self._writeLine(myS, '</reference>') |
|
855 self.close(myS) |
|
856 |
|
857 ########################## |
|
858 # End: Output |
|
859 ########################## |
|
860 |
|
861 def main(): |
|
862 usage = """usage: %prog [options] file_or_dir |
|
863 Takes a XSD file or directory and generates a packagedef.dita file with |
|
864 the documentation for the XSD file(s).""" |
|
865 print 'Cmd: %s' % ' '.join(sys.argv) |
|
866 optParser = OptionParser(usage, version='%prog ' + __version__) |
|
867 optParser.add_option("-d", action="store_true", dest="dump", default=False, |
|
868 help="Dump IR (for debugging). [default: %default]") |
|
869 optParser.add_option( |
|
870 "-l", "--loglevel", |
|
871 type="int", |
|
872 dest="loglevel", |
|
873 default=20, |
|
874 help="Log Level (debug=10, info=20, warning=30, error=40, critical=50) [default: %default]" |
|
875 ) |
|
876 optParser.add_option("-o", "--out", |
|
877 type="string", |
|
878 dest="output", |
|
879 default=None, |
|
880 help="Output file. [default: %default]") |
|
881 #=============================================================================== |
|
882 # optParser.add_option("-u", "--unittest", |
|
883 # action="store_true", |
|
884 # dest="unit_test", |
|
885 # default=False, |
|
886 # help="Execute unit tests. [default: %default]") |
|
887 #=============================================================================== |
|
888 optParser.add_option("-t", "--title", type="string", dest="title", |
|
889 default='C++ API Reference Content Model Definitions', |
|
890 help="Title for the packagedef.dita. [default: %default]") |
|
891 optParser.add_option("-p", "--prefix", type="string", dest="prefix", |
|
892 default='cxx', |
|
893 help="Prefix, only the elements starting with this will be documented. Use '' for all. [default: %default]") |
|
894 opts, args = optParser.parse_args() |
|
895 clkStart = time.clock() |
|
896 # Initialise logging etc. |
|
897 logging.basicConfig(level=opts.loglevel, |
|
898 format='%(asctime)s %(levelname)-8s %(message)s', |
|
899 #datefmt='%y-%m-%d % %H:%M:%S', |
|
900 stream=sys.stdout) |
|
901 #=============================================================================== |
|
902 # if opts.unit_test: |
|
903 # unitTest() |
|
904 #=============================================================================== |
|
905 if len(args) > 0: |
|
906 # Your code here |
|
907 myX = XsdToPackageDef(opts.prefix) |
|
908 for aFile in args: |
|
909 myX.addXsdPath(aFile) |
|
910 myX.writeToFile(opts.output, opts.title) |
|
911 if opts.dump: |
|
912 myX.dump() |
|
913 else: |
|
914 optParser.print_help() |
|
915 optParser.error("No arguments!") |
|
916 return 1 |
|
917 clkExec = time.clock() - clkStart |
|
918 print 'CPU time = %8.3f (S)' % clkExec |
|
919 print 'Bye, bye!' |
|
920 return 0 |
|
921 |
|
922 if __name__ == '__main__': |
|
923 #multiprocessing.freeze_support() |
|
924 sys.exit(main()) |
|
925 |