1#
2# Copyright 2009, Alexandre Deckner, alex@zappotek.com
3# Distributed under the terms of the MIT License.
4#
5
6try:
7    # deprecated in 3.2 (in favor of html.escape), removed in 3.8
8    from cgi import escape
9except ImportError:
10    from html import escape
11
12
13# prints match to stdout
14def printMatch(name, match, source):
15    start = match.start()
16    end = match.end()
17    startLine = source.count('\n', 0, start)
18    startColumn = start - source.rfind('\n', 0, start)
19    print(name + " (line " + str(startLine + 1) + ", " + str(startColumn) \
20        + "): '" + match.group().replace('\n','\\n') + "'")
21
22
23def openHtml(fileList, outputFileName):
24    file = open(outputFileName, 'w')
25    file.write("""
26    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
27        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
28    <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
29    <head>
30        <title>Style violations</title>
31        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
32        <style type="text/css">""" + cssStyle() + """</style>
33    </head>
34    <body>
35        <p><b>File list:</b><br/>""")
36    for fileName in fileList:
37        file.write(fileName + "<br/>")
38    file.write("</p>")
39    file.close()
40
41
42def closeHtml(outputFileName):
43    file = open(outputFileName, 'a')
44    file.write("""
45    </body>
46    </html>""")
47
48    file.close()
49
50
51# render in html
52def renderHtml(text, highlights, sourceFileName, outputFileName):
53    splittedText = highlightSplit(text, highlights)
54
55    file = open(outputFileName, 'a')
56    file.write("<hr/><p><b>" + sourceFileName + "</b></p>")
57
58    # insert highlight tags in a temp buffer
59    temp = ""
60    count = 0
61    for slice in splittedText:
62        if count % 2 == 0:
63            temp += escape(slice) + '<span class="highlight tooltip">'
64        else:
65            temp += escape(slice) + "<em>" + highlights[(count - 1) // 2][2] \
66                + "</em></span>"
67        count += 1
68
69    temp += "</span>" # close the superfluous last highlight
70
71    file.write('<table><tr><td><pre class="code"><span class="linenumber">')
72    count = 1
73    for line in temp.split('\n'):
74        file.write(str(count).rjust(4)+"<br/>")
75        count += 1
76
77    file.write('</span></pre></td><td><pre class="code">')
78
79    for line in temp.split('\n'):
80        file.write('<span class="linehead"> </span>' + line.replace('\r', ' ') \
81             + '<br/>')
82
83    file.write("</pre></td></tr></table>")
84
85    file.close()
86
87
88# highlight overlap check
89def highlightOverlaps(highlight1, highlight2):
90    #print("hl1", highlight1, "hl2", highlight2)
91    return not(highlight2[0] > highlight1[1] or highlight1[0] > highlight2[1])
92
93
94# splits the string in three parts before, between and after the highlight
95def splitByHighlight(string, highlight):
96    return (string[:highlight[0]], string[highlight[0]:highlight[1]], \
97        string[highlight[1]:])
98
99
100# splits the source text on highlights boundaries so that we can escape to html
101# without the need to recalculate the highlights positions
102def highlightSplit(string, highlights):
103    splittedString = []
104    text = string
105    offset = 0
106    lastEnd = 0
107    for (start, end, name) in highlights:
108         if start >= lastEnd:
109            (before, between, after) = splitByHighlight( \
110                text, (start - offset, end - offset))
111            splittedString.append(before)
112            splittedString.append(between)
113            text = after
114            lastEnd = end
115            offset += len(before + between)
116         else:
117            print("overlap ", (start, end, name))
118    splittedString.append(text)
119    return splittedString
120
121
122# checkHighlights() checks for highlights overlaps
123def checkHighlights(highlights):
124    highlights.sort()
125
126    index = 0
127    lastHighlight = (-2, -1, '')
128
129    # merge overlapping highlights
130    for highlight in highlights:
131        if highlightOverlaps(highlight, lastHighlight):
132
133            newStart = min(lastHighlight[0], highlight[0])
134            newEnd = max(lastHighlight[1], highlight[1])
135            newComment = lastHighlight[2]
136
137            if (newComment.find(highlight[2]) == -1):
138                newComment += " + " + highlight[2]
139
140            highlight = (newStart, newEnd, newComment)
141            highlights[index] = highlight
142
143            # mark highlight to be deleted
144            highlights[index - 1] = (0, 0, "")
145
146        lastHighlight = highlight
147        index += 1
148
149    # remove "to be deleted" highlights
150    return [ (start, end, comment) for (start, end, comment) in highlights \
151        if (start, end, comment) != (0, 0, "") ]
152
153
154def cssStyle():
155    return """
156    .highlight {
157        background: #ffff00;
158        color: #000000;
159    }
160
161    .linehead {
162        background: #ddd;
163        font-size: 1px;
164    }
165
166    .highlight .linehead {
167        background: #ffff00;;
168        color: #999;
169        text-align: right;
170        font-size: 8px;
171    }
172
173    .linenumber {
174        background: #eee;
175        color: #999;
176        text-align: right;
177    }
178
179    td {
180        border-spacing: 0px;
181        border-width: 0px;
182        padding: 0px
183    }
184
185    div.code pre {
186        font-family: monospace;
187    }
188
189    .tooltip em {
190        display:none;
191    }
192
193    .tooltip:hover {
194        border: 0;
195        position: relative;
196        z-index: 500;
197        text-decoration:none;
198    }
199
200    .tooltip:hover em {
201        font-style: normal;
202        display: block;
203        position: absolute;
204        top: 20px;
205        left: -10px;
206        padding: 5px;
207        color: #000;
208        border: 1px solid #bbb;
209        background: #ffc;
210        width: auto;
211    }"""
212