]> git.stg.codes - stg.git/blob - doc/xslt/extensions/docbook.py
Minor ChangeLog fix
[stg.git] / doc / xslt / extensions / docbook.py
1 # docbook.py: extension module
2 # $Id: docbook.py 8353 2009-03-17 16:57:50Z mzjn $
3
4 import sys
5 import string
6 import libxml2
7 import libxslt
8 import re
9 import math
10
11 # Some globals
12 pixelsPerInch = 96.0
13 unitHash = { 'in': pixelsPerInch,
14              'cm': pixelsPerInch / 2.54,
15              'mm': pixelsPerInch / 25.4,
16              'pc': (pixelsPerInch / 72.0) * 12,
17              'pt': pixelsPerInch / 72.0,
18              'px': 1 }
19
20 # ======================================================================
21
22 def adjustColumnWidths(ctx, nodeset):
23     #
24     # Small check to verify the context is correcly accessed
25     #
26     try:
27         pctxt = libxslt.xpathParserContext(_obj=ctx)
28         ctxt = pctxt.context()
29         tctxt = ctxt.transformContext()
30     except:
31         pass
32
33     # Get the nominal table width
34     varString = lookupVariable(tctxt, "nominal.table.width", None)
35     if varString == None:
36         nominalWidth = 6 * pixelsPerInch;
37     else:
38         nominalWidth = convertLength(varString);
39
40     # Get the requested table width
41     tableWidth = lookupVariable(tctxt, "table.width", "100%")
42
43     foStylesheet = (tctxt.variableLookup("stylesheet.result.type", None) == "fo")
44
45     relTotal = 0
46     relParts = []
47
48     absTotal = 0
49     absParts = []
50
51     colgroup = libxml2.xmlNode(_obj = nodeset[0])
52     # If this is an foStylesheet, we've been passed a list of fo:table-columns.
53     # Otherwise we've been passed a colgroup that contains a list of cols.
54     if foStylesheet:
55         colChildren = colgroup
56     else:
57         colChildren = colgroup.children
58
59     col = colChildren
60     while col != None:
61         if foStylesheet:
62             width = col.prop("column-width")
63         else:
64             width = col.prop("width")
65
66         if width == None:
67             width = "1*"
68
69         relPart = 0.0
70         absPart = 0.0
71         starPos = string.find(width, "*")
72         if starPos >= 0:
73             relPart, absPart = string.split(width, "*", 2)
74             relPart = float(relPart)
75             relTotal = relTotal + float(relPart)
76         else:
77             absPart = width
78
79         pixels = convertLength(absPart)
80         absTotal = absTotal + pixels
81
82         relParts.append(relPart)
83         absParts.append(pixels)
84
85         col = col.next
86
87     # Ok, now we have the relative widths and absolute widths in
88     # two parallel arrays.
89     #
90     # - If there are no relative widths, output the absolute widths
91     # - If there are no absolute widths, output the relative widths
92     # - If there are a mixture of relative and absolute widths,
93     #   - If the table width is absolute, turn these all into absolute
94     #     widths.
95     #   - If the table width is relative, turn these all into absolute
96     #     widths in the nominalWidth and then turn them back into
97     #     percentages.
98
99     widths = []
100
101     if relTotal == 0:
102         for absPart in absParts:
103             if foStylesheet:
104                 inches = absPart / pixelsPerInch
105                 widths.append("%4.2fin" % inches)
106             else:
107                 widths.append("%d" % absPart)
108     elif absTotal == 0:
109         for relPart in relParts:
110             rel = relPart / relTotal * 100
111             widths.append(rel)
112         widths = correctRoundingError(widths)
113     else:
114         pixelWidth = nominalWidth
115         if string.find(tableWidth, "%") < 0:
116             pixelWidth = convertLength(tableWidth)
117
118         if pixelWidth <= absTotal:
119             print "Table is wider than table width"
120         else:
121             pixelWidth = pixelWidth - absTotal
122
123         absTotal = 0
124         for count in range(len(relParts)):
125             rel = relParts[count] / relTotal * pixelWidth
126             relParts[count] = rel + absParts[count]
127             absTotal = absTotal + rel + absParts[count]
128
129         if string.find(tableWidth, "%") < 0:
130             for count in range(len(relParts)):
131                 if foStylesheet:
132                     pixels = relParts[count]
133                     inches = pixels / pixelsPerInch
134                     widths.append("%4.2fin" % inches)
135                 else:
136                     widths.append(relParts[count])
137         else:
138             for count in range(len(relParts)):
139                 rel = relParts[count] / absTotal * 100
140                 widths.append(rel)
141             widths = correctRoundingError(widths)
142
143     # Danger, Will Robinson! In-place modification of the result tree!
144     # Side-effect free? We don' need no steenkin' side-effect free!
145     count = 0
146     col = colChildren
147     while col != None:
148         if foStylesheet:
149             col.setProp("column-width", widths[count])
150         else:
151             col.setProp("width", widths[count])
152
153         count = count+1
154         col = col.next
155
156     return nodeset
157
158 def convertLength(length):
159     # Given "3.4in" return the width in pixels
160     global pixelsPerInch
161     global unitHash
162
163     m = re.search('([+-]?[\d\.]+)(\S+)', length)
164     if m != None and m.lastindex > 1:
165         unit = pixelsPerInch
166         if unitHash.has_key(m.group(2)):
167             unit = unitHash[m.group(2)]
168         else:
169             print "Unrecognized length: " + m.group(2)
170
171         pixels = unit * float(m.group(1))
172     else:
173         pixels = 0
174
175     return pixels
176
177 def correctRoundingError(floatWidths):
178     # The widths are currently floating point numbers, we have to truncate
179     # them back to integers and then distribute the error so that they sum
180     # to exactly 100%.
181
182     totalWidth = 0
183     widths = []
184     for width in floatWidths:
185         width = math.floor(width)
186         widths.append(width)
187         totalWidth = totalWidth + math.floor(width)
188
189     totalError = 100 - totalWidth
190     columnError = totalError / len(widths)
191     error = 0
192     for count in range(len(widths)):
193         width = widths[count]
194         error = error + columnError
195         if error >= 1.0:
196             adj = math.floor(error)
197             error = error - adj
198             widths[count] = "%d%%" % (width + adj)
199         else:
200             widths[count] = "%d%%" % width
201
202     return widths
203
204 def lookupVariable(tctxt, varName, default):
205     varString = tctxt.variableLookup(varName, None)
206     if varString == None:
207         return default
208
209     # If it's a list, get the first element
210     if type(varString) == type([]):
211         varString = varString[0]
212
213     # If it's not a string, it must be a node, get its content
214     if type(varString) != type(""):
215         varString = varString.content
216
217     return varString
218
219 # ======================================================================
220 # Random notes...
221
222 #once you have a node which is a libxml2 python xmlNode wrapper all common
223 #operations are possible:
224 #   .children .last .parent .next .prev .doc for navigation
225 #   .content .type for introspection
226 #   .prop("attribute_name") to lookup attribute values
227
228 #    # Now make a nodeset to return
229 #    # Danger, Will Robinson! This creates a memory leak!
230 #    newDoc = libxml2.newDoc("1.0")
231 #    newColGroup = newDoc.newDocNode(None, "colgroup", None)
232 #    newDoc.addChild(newColGroup)
233 #    col = colgroup.children
234 #    while col != None:
235 #        newCol = newDoc.newDocNode(None, "col", None)
236 #        newCol.copyPropList(col);
237 #        newCol.setProp("width", "4")
238 #        newColGroup.addChild(newCol)
239 #        col = col.next