woff 1.0.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.codeclimate.yml +24 -0
- data/.gitignore +2 -0
- data/.rubocop.yml +1156 -0
- data/.travis.yml +13 -0
- data/README.md +25 -4
- data/Rakefile +5 -0
- data/bin/rake +17 -0
- data/lib/woff.rb +8 -1
- data/lib/woff/builder.rb +66 -15
- data/lib/woff/file.rb +152 -0
- data/lib/woff/version.rb +1 -1
- data/requirements.txt +1 -0
- data/spec/builder_spec.rb +49 -0
- data/spec/data/font-with-no-metadata.woff +0 -0
- data/spec/spec_helper.rb +7 -0
- data/woff.gemspec +9 -2
- data/woffTools/Lib/woffTools/__init__.py +1176 -0
- data/woffTools/Lib/woffTools/test/__init__.py +0 -0
- data/woffTools/Lib/woffTools/test/test_validate.py +2657 -0
- data/woffTools/Lib/woffTools/tools/__init__.py +0 -0
- data/woffTools/Lib/woffTools/tools/css.py +292 -0
- data/woffTools/Lib/woffTools/tools/info.py +296 -0
- data/woffTools/Lib/woffTools/tools/proof.py +210 -0
- data/woffTools/Lib/woffTools/tools/support.py +417 -0
- data/woffTools/Lib/woffTools/tools/validate.py +2504 -0
- data/woffTools/License.txt +21 -0
- data/woffTools/README.txt +31 -0
- data/woffTools/setup.py +35 -0
- data/woffTools/woff-all +28 -0
- data/woffTools/woff-css +5 -0
- data/woffTools/woff-info +5 -0
- data/woffTools/woff-proof +5 -0
- data/woffTools/woff-validate +5 -0
- metadata +94 -9
- data/lib/woff/data.rb +0 -44
@@ -0,0 +1,210 @@
|
|
1
|
+
"""
|
2
|
+
A module for automatically creating simple proof files from
|
3
|
+
WOFF files. *proofFont* is the only public function.
|
4
|
+
|
5
|
+
This can also be used as a command line tool for generating
|
6
|
+
proofs from WOFF files.
|
7
|
+
"""
|
8
|
+
|
9
|
+
# import test
|
10
|
+
|
11
|
+
importErrors = []
|
12
|
+
try:
|
13
|
+
import numpy
|
14
|
+
except:
|
15
|
+
importErrors.append("numpy")
|
16
|
+
try:
|
17
|
+
import fontTools
|
18
|
+
except ImportError:
|
19
|
+
importErrors.append("fontTools")
|
20
|
+
try:
|
21
|
+
import woffTools
|
22
|
+
except ImportError:
|
23
|
+
importErrors.append("woffTools")
|
24
|
+
|
25
|
+
if importErrors:
|
26
|
+
import sys
|
27
|
+
print "Could not import needed module(s):", ", ".join(importErrors)
|
28
|
+
sys.exit()
|
29
|
+
|
30
|
+
# import
|
31
|
+
|
32
|
+
import os
|
33
|
+
import optparse
|
34
|
+
import unicodedata
|
35
|
+
from woffTools import WOFFFont
|
36
|
+
from woffTools.tools.css import makeFontFaceRule, makeFontFaceFontFamily
|
37
|
+
from woffTools.tools.support import startHTML, finishHTML, findUniqueFileName
|
38
|
+
|
39
|
+
|
40
|
+
# ----------------
|
41
|
+
# Report Functions
|
42
|
+
# ----------------
|
43
|
+
|
44
|
+
def writeFileInfo(font, fontPath, writer):
|
45
|
+
# start the block
|
46
|
+
writer.begintag("div", c_l_a_s_s="infoBlock")
|
47
|
+
# title
|
48
|
+
writer.begintag("h3", c_l_a_s_s="infoBlockTitle")
|
49
|
+
writer.write("File Information")
|
50
|
+
writer.endtag("h3")
|
51
|
+
# table
|
52
|
+
writer.begintag("table", c_l_a_s_s="report")
|
53
|
+
writeFileInfoRow("FILE", os.path.basename(fontPath), writer)
|
54
|
+
writeFileInfoRow("DIRECTORY", os.path.dirname(fontPath), writer)
|
55
|
+
writeFileInfoRow("VERSION", "%d.%d" % (font.majorVersion, font.minorVersion), writer)
|
56
|
+
writer.endtag("table")
|
57
|
+
# close the container
|
58
|
+
writer.endtag("div")
|
59
|
+
|
60
|
+
def writeFileInfoRow(title, value, writer):
|
61
|
+
# row
|
62
|
+
writer.begintag("tr")
|
63
|
+
# title
|
64
|
+
writer.begintag("td", c_l_a_s_s="title")
|
65
|
+
writer.write(title)
|
66
|
+
writer.endtag("td")
|
67
|
+
# message
|
68
|
+
writer.begintag("td")
|
69
|
+
writer.write(value)
|
70
|
+
writer.endtag("td")
|
71
|
+
# close row
|
72
|
+
writer.endtag("tr")
|
73
|
+
|
74
|
+
def writeCharacterSet(font, writer, pointSizes=[9, 10, 11, 12, 14, 18, 24, 36, 48, 72], sampleText=None):
|
75
|
+
characterSet = makeCharacterSet(font)
|
76
|
+
for size in pointSizes:
|
77
|
+
# start the block
|
78
|
+
writer.begintag("div", c_l_a_s_s="infoBlock")
|
79
|
+
# title
|
80
|
+
writer.begintag("h4", c_l_a_s_s="infoBlockTitle")
|
81
|
+
writer.write("%dpx" % size)
|
82
|
+
writer.endtag("h4")
|
83
|
+
# character set
|
84
|
+
writer.begintag("p", style="font-size: %dpx;" % size, c_l_a_s_s="characterSet")
|
85
|
+
writer.write(characterSet)
|
86
|
+
writer.endtag("p")
|
87
|
+
# sample text
|
88
|
+
if sampleText:
|
89
|
+
writer.begintag("p", style="font-size: %dpx;" % size, c_l_a_s_s="sampleText")
|
90
|
+
writer.write(sampleText)
|
91
|
+
writer.endtag("p")
|
92
|
+
# close the container
|
93
|
+
writer.endtag("div")
|
94
|
+
|
95
|
+
# -------------
|
96
|
+
# Character Set
|
97
|
+
# -------------
|
98
|
+
|
99
|
+
def makeCharacterSet(font):
|
100
|
+
cmap = font["cmap"]
|
101
|
+
table = cmap.getcmap(3, 1)
|
102
|
+
mapping = table.cmap
|
103
|
+
categorizedCharacters = {}
|
104
|
+
glyphNameToCharacter = {}
|
105
|
+
for value, glyphName in sorted(mapping.items()):
|
106
|
+
character = unichr(value)
|
107
|
+
# skip whitespace
|
108
|
+
if not character.strip():
|
109
|
+
continue
|
110
|
+
if glyphName not in glyphNameToCharacter:
|
111
|
+
glyphNameToCharacter[glyphName] = []
|
112
|
+
glyphNameToCharacter[glyphName].append(character)
|
113
|
+
# use the glyph order defined in the font
|
114
|
+
sortedCharacters = []
|
115
|
+
for glyphName in font.getGlyphOrder():
|
116
|
+
if glyphName in glyphNameToCharacter:
|
117
|
+
sortedCharacters += glyphNameToCharacter[glyphName]
|
118
|
+
return u"".join(sortedCharacters)
|
119
|
+
|
120
|
+
# ---------------
|
121
|
+
# Public Function
|
122
|
+
# ---------------
|
123
|
+
|
124
|
+
def proofFont(font, fontPath, sampleText=None):
|
125
|
+
"""
|
126
|
+
Create a proof file from the given font. This always
|
127
|
+
returns HTML.
|
128
|
+
|
129
|
+
Arguments:
|
130
|
+
**font** - A *WOFFFont* object from *woffLib*.
|
131
|
+
**fontPath** - The location of the font file. At the least, this should be the file name for the font.
|
132
|
+
**sampleText** - A string of text to display. If not provided, no text will be displayed.
|
133
|
+
"""
|
134
|
+
# start the html
|
135
|
+
title = "Proof: %s" % os.path.basename(fontPath)
|
136
|
+
cssReplacements = {
|
137
|
+
"/* proof: @font-face rule */" : makeFontFaceRule(font, fontPath, doLocalSrc=False),
|
138
|
+
"/* proof: @font-face font-family */" : makeFontFaceFontFamily(font)
|
139
|
+
}
|
140
|
+
writer = startHTML(title=title, cssReplacements=cssReplacements)
|
141
|
+
# file info
|
142
|
+
writeFileInfo(font, fontPath, writer)
|
143
|
+
# character set
|
144
|
+
writeCharacterSet(font, writer, sampleText=sampleText)
|
145
|
+
# finish the html
|
146
|
+
text = finishHTML(writer)
|
147
|
+
text = text.replace("%%break%%", "</br>")
|
148
|
+
# return
|
149
|
+
return text
|
150
|
+
|
151
|
+
# --------------------
|
152
|
+
# Command Line Behvior
|
153
|
+
# --------------------
|
154
|
+
|
155
|
+
usage = "%prog [options] fontpath1 fontpath2"
|
156
|
+
|
157
|
+
description = """This tool displays information about the
|
158
|
+
contents of one or more WOFF files.
|
159
|
+
"""
|
160
|
+
|
161
|
+
defaultSampleText = "THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG. The quick brown fox jumps over the lazy dog."
|
162
|
+
|
163
|
+
def main():
|
164
|
+
parser = optparse.OptionParser(usage=usage, description=description, version="%prog 0.1beta")
|
165
|
+
parser.add_option("-d", dest="outputDirectory", help="Output directory. The default is to output the proof into the same directory as the font file.")
|
166
|
+
parser.add_option("-o", dest="outputFileName", help="Output file name. The default is \"fontfilename_proof.html\".")
|
167
|
+
parser.add_option("-t", dest="sampleTextFile", help="Sample text file. A file containing sample text to display. If not file is provided, The quick brown fox... will be used.")
|
168
|
+
parser.set_defaults(excludeTests=[])
|
169
|
+
(options, args) = parser.parse_args()
|
170
|
+
outputDirectory = options.outputDirectory
|
171
|
+
if outputDirectory is not None and not os.path.exists(outputDirectory):
|
172
|
+
print "Directory does not exist:", outputDirectory
|
173
|
+
sys.exit()
|
174
|
+
sampleText = defaultSampleText
|
175
|
+
if options.sampleTextFile:
|
176
|
+
if not os.path.exists(options.sampleTextFile):
|
177
|
+
print "Sample text file does not exist:", options.sampleTextFile
|
178
|
+
sys.exit()
|
179
|
+
f = open(options.sampleTextFile, "r")
|
180
|
+
sampleText = f.read()
|
181
|
+
f.close()
|
182
|
+
for fontPath in args:
|
183
|
+
if not os.path.exists(fontPath):
|
184
|
+
print "File does not exist:", fontPath
|
185
|
+
sys.exit()
|
186
|
+
else:
|
187
|
+
print "Creating Proof: %s..." % fontPath
|
188
|
+
fontPath = fontPath.decode("utf-8")
|
189
|
+
font = WOFFFont(fontPath)
|
190
|
+
html = proofFont(font, fontPath, sampleText=sampleText)
|
191
|
+
# make the output file name
|
192
|
+
if options.outputFileName is not None:
|
193
|
+
fileName = options.outputFileName
|
194
|
+
else:
|
195
|
+
fileName = os.path.splitext(os.path.basename(fontPath))[0]
|
196
|
+
fileName += "_proof.html"
|
197
|
+
# make the output directory
|
198
|
+
if options.outputDirectory is not None:
|
199
|
+
directory = options.outputDirectory
|
200
|
+
else:
|
201
|
+
directory = os.path.dirname(fontPath)
|
202
|
+
# write the file
|
203
|
+
path = os.path.join(directory, fileName)
|
204
|
+
path = findUniqueFileName(path)
|
205
|
+
f = open(path, "wb")
|
206
|
+
f.write(html)
|
207
|
+
f.close()
|
208
|
+
|
209
|
+
if __name__ == "__main__":
|
210
|
+
main()
|
@@ -0,0 +1,417 @@
|
|
1
|
+
import os
|
2
|
+
import time
|
3
|
+
from xml.etree import ElementTree
|
4
|
+
from cStringIO import StringIO
|
5
|
+
|
6
|
+
# ----------------------
|
7
|
+
# Very Simple XML Writer
|
8
|
+
# ----------------------
|
9
|
+
|
10
|
+
class XMLWriter(object):
|
11
|
+
|
12
|
+
def __init__(self):
|
13
|
+
self._root = None
|
14
|
+
self._elements = []
|
15
|
+
|
16
|
+
def simpletag(self, tag, **kwargs):
|
17
|
+
ElementTree.SubElement(self._elements[-1], tag, **kwargs)
|
18
|
+
|
19
|
+
def begintag(self, tag, **kwargs):
|
20
|
+
if self._elements:
|
21
|
+
s = ElementTree.SubElement(self._elements[-1], tag, **kwargs)
|
22
|
+
else:
|
23
|
+
s = ElementTree.Element(tag, **kwargs)
|
24
|
+
if self._root is None:
|
25
|
+
self._root = s
|
26
|
+
self._elements.append(s)
|
27
|
+
|
28
|
+
def endtag(self, tag):
|
29
|
+
assert self._elements[-1].tag == tag
|
30
|
+
del self._elements[-1]
|
31
|
+
|
32
|
+
def write(self, text):
|
33
|
+
if self._elements[-1].text is None:
|
34
|
+
self._elements[-1].text = text
|
35
|
+
else:
|
36
|
+
self._elements[-1].text += text
|
37
|
+
|
38
|
+
def compile(self, encoding="utf-8"):
|
39
|
+
f = StringIO()
|
40
|
+
tree = ElementTree.ElementTree(self._root)
|
41
|
+
indent(tree.getroot())
|
42
|
+
tree.write(f, encoding=encoding)
|
43
|
+
text = f.getvalue()
|
44
|
+
del f
|
45
|
+
return text
|
46
|
+
|
47
|
+
def indent(elem, level=0):
|
48
|
+
# this is from http://effbot.python-hosting.com/file/effbotlib/ElementTree.py
|
49
|
+
i = "\n" + level * "\t"
|
50
|
+
if len(elem):
|
51
|
+
if not elem.text or not elem.text.strip():
|
52
|
+
elem.text = i + "\t"
|
53
|
+
for e in elem:
|
54
|
+
indent(e, level + 1)
|
55
|
+
if not e.tail or not e.tail.strip():
|
56
|
+
e.tail = i
|
57
|
+
if level and (not elem.tail or not elem.tail.strip()):
|
58
|
+
elem.tail = i
|
59
|
+
|
60
|
+
# ------------
|
61
|
+
# HTML Helpers
|
62
|
+
# ------------
|
63
|
+
|
64
|
+
defaultCSS = """
|
65
|
+
body {
|
66
|
+
background-color: #e5e5e5;
|
67
|
+
padding: 15px 15px 0px 15px;
|
68
|
+
margin: 0px;
|
69
|
+
font-family: Helvetica, Verdana, Arial, sans-serif;
|
70
|
+
}
|
71
|
+
|
72
|
+
h2.readError {
|
73
|
+
background-color: red;
|
74
|
+
color: white;
|
75
|
+
margin: 20px 15px 20px 15px;
|
76
|
+
padding: 10px;
|
77
|
+
border-radius: 5px;
|
78
|
+
-webkit-border-radius: 5px;
|
79
|
+
-moz-border-radius: 5px;
|
80
|
+
-webkit-box-shadow: #999 0 2px 5px;
|
81
|
+
-moz-box-shadow: #999 0 2px 5px;
|
82
|
+
font-size: 25px;
|
83
|
+
}
|
84
|
+
|
85
|
+
/* info blocks */
|
86
|
+
|
87
|
+
.infoBlock {
|
88
|
+
background-color: white;
|
89
|
+
margin: 0px 0px 15px 0px;
|
90
|
+
padding: 15px;
|
91
|
+
border-radius: 5px;
|
92
|
+
-webkit-border-radius: 5px;
|
93
|
+
-moz-border-radius: 5px;
|
94
|
+
-webkit-box-shadow: rgba(0, 0, 0, .3) 0 2px 5px;
|
95
|
+
-moz-box-shadow: rgba(0, 0, 0, .3) 0 2px 5px;
|
96
|
+
}
|
97
|
+
|
98
|
+
h3.infoBlockTitle {
|
99
|
+
font-size: 20px;
|
100
|
+
margin: 0px 0px 15px 0px;
|
101
|
+
padding: 0px 0px 10px 0px;
|
102
|
+
border-bottom: 1px solid #e5e5e5;
|
103
|
+
}
|
104
|
+
|
105
|
+
h4.infoBlockTitle {
|
106
|
+
font-size: 17px;
|
107
|
+
margin: 0px 0px 15px 0px;
|
108
|
+
padding: 0px 0px 10px 0px;
|
109
|
+
border-bottom: 1px solid #e5e5e5;
|
110
|
+
}
|
111
|
+
|
112
|
+
table.report {
|
113
|
+
border-collapse: collapse;
|
114
|
+
width: 100%;
|
115
|
+
font-size: 14px;
|
116
|
+
}
|
117
|
+
|
118
|
+
table.report tr {
|
119
|
+
border-top: 1px solid white;
|
120
|
+
}
|
121
|
+
|
122
|
+
table.report tr.testPass, table.report tr.testReportPass {
|
123
|
+
background-color: #c8ffaf;
|
124
|
+
}
|
125
|
+
|
126
|
+
table.report tr.testError, table.report tr.testReportError {
|
127
|
+
background-color: #ffc3af;
|
128
|
+
}
|
129
|
+
|
130
|
+
table.report tr.testWarning, table.report tr.testReportWarning {
|
131
|
+
background-color: #ffe1af;
|
132
|
+
}
|
133
|
+
|
134
|
+
table.report tr.testNote, table.report tr.testReportNote {
|
135
|
+
background-color: #96e1ff;
|
136
|
+
}
|
137
|
+
|
138
|
+
table.report tr.testTraceback, table.report tr.testReportTraceback {
|
139
|
+
background-color: red;
|
140
|
+
color: white;
|
141
|
+
}
|
142
|
+
|
143
|
+
table.report td {
|
144
|
+
padding: 7px 5px 7px 5px;
|
145
|
+
vertical-align: top;
|
146
|
+
}
|
147
|
+
|
148
|
+
table.report td.title {
|
149
|
+
width: 80px;
|
150
|
+
text-align: right;
|
151
|
+
font-weight: bold;
|
152
|
+
text-transform: uppercase;
|
153
|
+
}
|
154
|
+
|
155
|
+
table.report td.testReportResultCount {
|
156
|
+
width: 100px;
|
157
|
+
}
|
158
|
+
|
159
|
+
table.report td.toggleButton {
|
160
|
+
text-align: center;
|
161
|
+
width: 50px;
|
162
|
+
border-left: 1px solid white;
|
163
|
+
cursor: pointer;
|
164
|
+
}
|
165
|
+
|
166
|
+
.infoBlock td p.info {
|
167
|
+
font-size: 12px;
|
168
|
+
font-style: italic;
|
169
|
+
margin: 5px 0px 0px 0px;
|
170
|
+
}
|
171
|
+
|
172
|
+
/* SFNT table */
|
173
|
+
|
174
|
+
table.sfntTableData {
|
175
|
+
font-size: 14px;
|
176
|
+
width: 100%;
|
177
|
+
border-collapse: collapse;
|
178
|
+
padding: 0px;
|
179
|
+
}
|
180
|
+
|
181
|
+
table.sfntTableData th {
|
182
|
+
padding: 5px 0px 5px 0px;
|
183
|
+
text-align: left
|
184
|
+
}
|
185
|
+
|
186
|
+
table.sfntTableData tr.uncompressed {
|
187
|
+
background-color: #ffc3af;
|
188
|
+
}
|
189
|
+
|
190
|
+
table.sfntTableData td {
|
191
|
+
width: 20%;
|
192
|
+
padding: 5px 0px 5px 0px;
|
193
|
+
border: 1px solid #e5e5e5;
|
194
|
+
border-left: none;
|
195
|
+
border-right: none;
|
196
|
+
font-family: Consolas, Menlo, "Vera Mono", Monaco, monospace;
|
197
|
+
}
|
198
|
+
|
199
|
+
pre {
|
200
|
+
font-size: 12px;
|
201
|
+
font-family: Consolas, Menlo, "Vera Mono", Monaco, monospace;
|
202
|
+
margin: 0px;
|
203
|
+
padding: 0px;
|
204
|
+
}
|
205
|
+
|
206
|
+
/* Metadata */
|
207
|
+
|
208
|
+
.metadataElement {
|
209
|
+
background: rgba(0, 0, 0, 0.03);
|
210
|
+
margin: 10px 0px 10px 0px;
|
211
|
+
border: 2px solid #d8d8d8;
|
212
|
+
padding: 10px;
|
213
|
+
}
|
214
|
+
|
215
|
+
h5.metadata {
|
216
|
+
font-size: 14px;
|
217
|
+
margin: 5px 0px 10px 0px;
|
218
|
+
padding: 0px 0px 5px 0px;
|
219
|
+
border-bottom: 1px solid #d8d8d8;
|
220
|
+
}
|
221
|
+
|
222
|
+
h6.metadata {
|
223
|
+
font-size: 12px;
|
224
|
+
font-weight: normal;
|
225
|
+
margin: 10px 0px 10px 0px;
|
226
|
+
padding: 0px 0px 5px 0px;
|
227
|
+
border-bottom: 1px solid #d8d8d8;
|
228
|
+
}
|
229
|
+
|
230
|
+
table.metadata {
|
231
|
+
font-size: 12px;
|
232
|
+
width: 100%;
|
233
|
+
border-collapse: collapse;
|
234
|
+
padding: 0px;
|
235
|
+
}
|
236
|
+
|
237
|
+
table.metadata td.key {
|
238
|
+
width: 5em;
|
239
|
+
padding: 5px 5px 5px 0px;
|
240
|
+
border-right: 1px solid #d8d8d8;
|
241
|
+
text-align: right;
|
242
|
+
vertical-align: top;
|
243
|
+
}
|
244
|
+
|
245
|
+
table.metadata td.value {
|
246
|
+
padding: 5px 0px 5px 5px;
|
247
|
+
border-left: 1px solid #d8d8d8;
|
248
|
+
text-align: left;
|
249
|
+
vertical-align: top;
|
250
|
+
}
|
251
|
+
|
252
|
+
p.metadata {
|
253
|
+
font-size: 12px;
|
254
|
+
font-style: italic;
|
255
|
+
}
|
256
|
+
|
257
|
+
/* Proof */
|
258
|
+
|
259
|
+
/* proof: @font-face rule */
|
260
|
+
|
261
|
+
p.characterSet {
|
262
|
+
/* proof: @font-face font-family */
|
263
|
+
line-height: 135%;
|
264
|
+
word-wrap: break-word;
|
265
|
+
margin: 0px;
|
266
|
+
padding: 0px;
|
267
|
+
}
|
268
|
+
|
269
|
+
p.sampleText {
|
270
|
+
/* proof: @font-face font-family */
|
271
|
+
line-height: 135%;
|
272
|
+
margin: .5em 0px 0px 0px;
|
273
|
+
padding: .5em 0px 0px 0px;
|
274
|
+
border-top: 1px solid #e5e5e5;
|
275
|
+
}
|
276
|
+
"""
|
277
|
+
|
278
|
+
defaultJavascript = """
|
279
|
+
|
280
|
+
//<![CDATA[
|
281
|
+
function testResultToggleButtonHit(buttonID, className) {
|
282
|
+
// change the button title
|
283
|
+
var element = document.getElementById(buttonID);
|
284
|
+
if (element.innerHTML == "Show" ) {
|
285
|
+
element.innerHTML = "Hide";
|
286
|
+
}
|
287
|
+
else {
|
288
|
+
element.innerHTML = "Show";
|
289
|
+
}
|
290
|
+
// toggle the elements
|
291
|
+
var elements = getTestResults(className);
|
292
|
+
for (var e = 0; e < elements.length; ++e) {
|
293
|
+
toggleElement(elements[e]);
|
294
|
+
}
|
295
|
+
// toggle the info blocks
|
296
|
+
toggleInfoBlocks();
|
297
|
+
}
|
298
|
+
|
299
|
+
function getTestResults(className) {
|
300
|
+
var rows = document.getElementsByTagName("tr");
|
301
|
+
var found = Array();
|
302
|
+
for (var r = 0; r < rows.length; ++r) {
|
303
|
+
var row = rows[r];
|
304
|
+
if (row.className == className) {
|
305
|
+
found[found.length] = row;
|
306
|
+
}
|
307
|
+
}
|
308
|
+
return found;
|
309
|
+
}
|
310
|
+
|
311
|
+
function toggleElement(element) {
|
312
|
+
if (element.style.display != "none" ) {
|
313
|
+
element.style.display = "none";
|
314
|
+
}
|
315
|
+
else {
|
316
|
+
element.style.display = "";
|
317
|
+
}
|
318
|
+
}
|
319
|
+
|
320
|
+
function toggleInfoBlocks() {
|
321
|
+
var tables = document.getElementsByTagName("table")
|
322
|
+
for (var t = 0; t < tables.length; ++t) {
|
323
|
+
var table = tables[t];
|
324
|
+
if (table.className == "report") {
|
325
|
+
var haveVisibleRow = false;
|
326
|
+
var rows = table.rows;
|
327
|
+
for (var r = 0; r < rows.length; ++r) {
|
328
|
+
var row = rows[r];
|
329
|
+
if (row.style.display == "none") {
|
330
|
+
var i = 0;
|
331
|
+
}
|
332
|
+
else {
|
333
|
+
haveVisibleRow = true;
|
334
|
+
}
|
335
|
+
}
|
336
|
+
var div = table.parentNode;
|
337
|
+
if (haveVisibleRow == true) {
|
338
|
+
div.style.display = "";
|
339
|
+
}
|
340
|
+
else {
|
341
|
+
div.style.display = "none";
|
342
|
+
}
|
343
|
+
}
|
344
|
+
}
|
345
|
+
}
|
346
|
+
//]]>
|
347
|
+
"""
|
348
|
+
|
349
|
+
def startHTML(title=None, cssReplacements={}):
|
350
|
+
writer = XMLWriter()
|
351
|
+
# start the html
|
352
|
+
writer.begintag("html", xmlns="http://www.w3.org/1999/xhtml", lang="en")
|
353
|
+
# start the head
|
354
|
+
writer.begintag("head")
|
355
|
+
writer.simpletag("meta", http_equiv="Content-Type", content="text/html; charset=utf-8")
|
356
|
+
# title
|
357
|
+
if title is not None:
|
358
|
+
writer.begintag("title")
|
359
|
+
writer.write(title)
|
360
|
+
writer.endtag("title")
|
361
|
+
# write the css
|
362
|
+
writer.begintag("style", type="text/css")
|
363
|
+
css = defaultCSS
|
364
|
+
for before, after in cssReplacements.items():
|
365
|
+
css = css.replace(before, after)
|
366
|
+
writer.write(css)
|
367
|
+
writer.endtag("style")
|
368
|
+
# write the javascript
|
369
|
+
writer.begintag("script", type="text/javascript")
|
370
|
+
javascript = defaultJavascript
|
371
|
+
## hack around some ElementTree escaping
|
372
|
+
javascript = javascript.replace("<", "l_e_s_s")
|
373
|
+
javascript = javascript.replace(">", "g_r_e_a_t_e_r")
|
374
|
+
writer.write(javascript)
|
375
|
+
writer.endtag("script")
|
376
|
+
# close the head
|
377
|
+
writer.endtag("head")
|
378
|
+
# start the body
|
379
|
+
writer.begintag("body")
|
380
|
+
# return the writer
|
381
|
+
return writer
|
382
|
+
|
383
|
+
def finishHTML(writer):
|
384
|
+
# close the body
|
385
|
+
writer.endtag("body")
|
386
|
+
# close the html
|
387
|
+
writer.endtag("html")
|
388
|
+
# get the text
|
389
|
+
text = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n"
|
390
|
+
text += writer.compile()
|
391
|
+
text = text.replace("c_l_a_s_s", "class")
|
392
|
+
text = text.replace("a_p_o_s_t_r_o_p_h_e", "'")
|
393
|
+
text = text.replace("l_e_s_s", "<")
|
394
|
+
text = text.replace("g_r_e_a_t_e_r", ">")
|
395
|
+
text = text.replace("http_equiv", "http-equiv")
|
396
|
+
# return
|
397
|
+
return text
|
398
|
+
|
399
|
+
# ---------
|
400
|
+
# File Name
|
401
|
+
# ---------
|
402
|
+
|
403
|
+
def findUniqueFileName(path):
|
404
|
+
if not os.path.exists(path):
|
405
|
+
return path
|
406
|
+
folder = os.path.dirname(path)
|
407
|
+
fileName = os.path.basename(path)
|
408
|
+
fileName, extension = os.path.splitext(fileName)
|
409
|
+
stamp = time.strftime("%Y-%m-%d %H-%M-%S %Z")
|
410
|
+
newFileName = "%s (%s)%s" % (fileName, stamp, extension)
|
411
|
+
newPath = os.path.join(folder, newFileName)
|
412
|
+
# intentionally break to prevent a file overwrite.
|
413
|
+
# this could happen if the user has a directory full
|
414
|
+
# of files with future time stamped file names.
|
415
|
+
# not likely, but avoid it all the same.
|
416
|
+
assert not os.path.exists(newPath)
|
417
|
+
return newPath
|