UTC encode datetime in results.csv
[fourmizzz.git] / htmlentities.py
1 #!/usr/bin/env python3
2 # -*- encoding: utf-8 -*-
3
4 __all__ = ['resolve', 'expand', 'cleanCDATA']
5
6 from html.entities import name2codepoint as entities
7
8 entities_autocomplete = {}
9 longestEntityLen = 0
10 for key,value in entities.items():
11     if value<=255:
12         entities_autocomplete[key] = value
13     l = len(key)
14     if l>longestEntityLen:
15         longestEntityLen = l
16
17 # Characters in range 127-159 are illegals, but they are sometimes wrongly used in web pages
18 # Internet Explorer assumes it is taken from Microsoft extension to Latin 1 page 8859-1 aka CP1512
19 # However, to be clean, we must remap them to their real unicode values
20 # Unknown codes are translated into a space
21 iso88591_remap = [
22         32,             # 127: ???
23         8364,   # 128: Euro symbol
24         32,             # 129: ???
25         8218,   # 130: Single Low-9 Quotation Mark
26         402,    # 131: Latin Small Letter F With Hook
27         8222,   # 132: Double Low-9 Quotation Mark
28         8230,   # 133: Horizontal Ellipsis
29         8224,   # 134: Dagger
30         8225,   # 135: Double Dagger
31         710,    # 136: Modifier Letter Circumflex Accent
32         8240,   # 137: Per Mille Sign
33         352,    # 138: Latin Capital Letter S With Caron
34         8249,   # 139: Single Left-Pointing Angle Quotation Mark
35         338,    # 140: Latin Capital Ligature OE
36         32,             # 141: ???
37         381,    # 142: Latin Capital Letter Z With Caron
38         32,             # 143: ???
39         32,             # 144: ???
40         8216,   # 145: Left Single Quotation Mark
41         8217,   # 146: Right Single Quotation Mark
42         8220,   # 147: Left Double Quotation Mark
43         8221,   # 148: Right Double Quotation Mark
44         8226,   # 149: Bullet
45         8211,   # 150: En Dash
46         8212,   # 151: Em Dash
47         732,    # 152: Small Tilde
48         8482,   # 153: Trade Mark Sign
49         353,    # 154: Latin Small Letter S With Caron
50         8250,   # 155: Single Right-Pointing Angle Quotation Mark
51         339,    # 156: Latin Small Ligature OE
52         32,             # 157: ???
53         382,    # 158: Latin Small Letter Z With Caron
54         376             # 159: Latin Capital Letter Y With Diaeresis
55 ]
56
57
58 def checkForUnicodeReservedChar(value):
59     if value >= 0xfffe:
60         return ord('?')
61     if value < 127 or value > 159:
62         return value
63     return iso88591_remap[value-127]
64
65 def expand(text):
66     result = ''
67     for c in text:
68         oc = ord(c)
69         oc = checkForUnicodeReservedChar(oc)
70         if oc<32 or c=='&' or c=='<' or c=='>' or c=='"' or oc>127:
71             result += '&#'+str(oc)+';'
72         else:
73             result += c
74     return result
75
76 def resolve(text):
77     pos = 0
78     result = ''
79     l = len(text)
80     while True:
81         prevpos = pos
82         pos = text.find('&', prevpos)
83         if pos == -1:
84             ## print "No more &"
85             break
86
87         if pos >= l-2:
88             ## print "Too shoort"
89             break
90                 # here we are sure the next two chars exist
91         
92         result += text[prevpos:pos]
93         c = text[pos+1]
94         if c == '#':
95             ## print "numeric entity"
96                         # This looks like an char whose unicode if given raw
97             c = text[pos+2]
98             if c == 'x' or c == 'X' and pos < l-3:
99                 tmppos = text.find(';', pos+3)
100                 if tmppos != -1:
101                     s = text[pos+3: tmppos]
102                     try:
103                         value = int(s, 16)
104                         value = checkForUnicodeReservedChar(value) # remap unicode char if in range 127-159
105                         result += chr(value)
106                         pos = tmppos + 1
107                         continue # ok, we did it
108                     except ValueError:
109                                             # there pos is not updated so that the original escape-like sequence is kept unchanged
110                         pass
111             else:
112                                 # the given unicode value is decimal
113                                 # IE behavior: parse until non digital char, no conversion if this is not
114                 sb = ''
115                 tmppos = pos+2
116                 while True:
117                     if tmppos >= l:
118                         break # out of range
119                     c = text[tmppos]
120                     if c == ';':
121                         tmppos += 1
122                         break
123                     if c<'0' or c>'9':
124                         break
125                     sb += c
126                     tmppos += 1
127                 try:
128                     value = int(sb)
129                     value = checkForUnicodeReservedChar(value); # remap unicode char if in range 127-159
130                     result += chr(value)
131                     pos = tmppos
132                     continue # ok, we did it
133                 except ValueError:
134                     # there pos is not updated so that the original escape-like sequence is kept unchanged
135                     pass
136         else:
137             # here the first character is not a '#'
138             # let's try the known html entities
139
140             sb = ''
141             tmppos = pos + 1
142             while True:
143                 if tmppos >= l or tmppos-pos > longestEntityLen + 1: # 1 more for ';'
144                     c2 = entities_autocomplete.get(sb, 0)
145                     break
146                 c = text[tmppos]
147                 if c == ';':
148                     tmppos += 1
149                     c2 = entities.get(sb, 0)
150                     break
151                 c2 = entities_autocomplete.get(sb, 0)
152                 if c2:
153                     break
154                 sb += c
155                 tmppos += 1
156             if c2:
157                 result += chr(c2)
158                 pos = tmppos
159                 continue # ok, we did it
160                         
161         result += '&' # something went wrong, just skip is '&'
162         pos += 1
163
164     result += text[prevpos:] 
165     return result
166
167 def cleanCDATA(text):
168     """
169     resolve entities
170     removes useless whites, \r, \n and \t with whites
171     expand back entities
172     """
173     tmp = resolve(text)
174     result = ''
175     isLastWhite = False # so that first white is not removed
176     for c in tmp:
177         if c in ' \r\n\t':
178             if not isLastWhite:
179                 result += ' '
180                 isLastWhite = True
181         else:
182             result += c
183             isLastWhite = False
184
185     return expand(result)
186
187 if __name__ == '__main__':
188     import sys
189     if len(sys.argv)<2:
190         print("Missing required parameter. Try '&amp;test'", file=sys.stderr)
191         sys.exit(1)
192     input = ' '.join(sys.argv[1:])
193     #print 'input:', input
194     #raw = resolve(input)
195     #print 'resolved:', raw
196     #print 'expanded:', expand(raw)
197     print('cleanCDATA:', cleanCDATA(input))
198