Handle ^C from prompt.
[tx] / xpath_prompt.py
1 #!/usr/bin/env python
2 # -*- coding:utf-8 -*-
3
4 # XPath prompt
5 # Copyright (C) 2005  Frédéric Jolliton <frederic@jolliton.com>
6
7 # This program is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 2 of the License, or
10 # (at your option) any later version.
11
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 # GNU General Public License for more details.
16
17 # You should have received a copy of the GNU General Public License
18 # along with this program; if not, write to the Free Software
19 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20
21 import re
22 import sys
23 import time
24 import urllib
25 import StringIO
26
27 import htmltree
28 from xpath import XPath
29 import xpathparser
30 from misc import guessXmlCharacterEncoding
31 from sequence import Sequence
32 from nodes import Node, Document
33 from error import Error
34 from parser import NoMatch
35 from sequence_misc import printSequence
36 from xpathfn import *
37
38 from xpath_misc import lispy
39
40 g_defaultUserAgent = 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)'
41
42 def printInlineSequence( sequence ) :
43
44         for item in sequence :
45                 if isinstance( item , Node ) :
46                         item.serialize( file = sys.stdout )
47                         print
48                 else :
49                         print item
50
51 rtXmlDeclVersionInfo    = r'''(?:\s+version=(?P<version>(?:'[^']*'|"[^"]*")))'''
52 rtXmlDeclEncodingDecl   = r'''(?:\s+encoding=(?P<encoding>(?:'[^']*'|"[^"]*")))'''
53 rtXmlDeclStandaloneDecl = r'''(?:\s+standalone=(?P<standalone>(?:'[^']*'|"[^"]*")))'''
54 reXmlDecl = re.compile( r'<\?xml(?:%s|%s|%s)*\s*\??>' \
55                                                 % ( rtXmlDeclVersionInfo ,
56                                                         rtXmlDeclEncodingDecl ,
57                                                         rtXmlDeclStandaloneDecl ) )
58 def decodeDocument( txt ) :
59
60         if not isinstance( txt , unicode ) :
61                 enc , skip = guessXmlCharacterEncoding( txt[ : 4 ] )
62                 dec = 'utf_8'
63                 if enc == 'UTF-8' :
64                         dec = 'utf_8'
65                 elif enc == '8BIT' :
66                         r = reXmlDecl.search( txt )
67                         if r is not None :
68                                 enc = r.groupdict()[ 'encoding' ]
69                                 if enc is not None :
70                                         enc = enc[ 1 : -1 ].lower()
71                                         if enc.startswith( 'iso-8859-' ) :
72                                                 dec = enc
73                                         elif enc in ( 'utf8' , 'utf-8' ) :
74                                                 dec = 'utf_8'
75                                         else :
76                                                 dec = None
77                 else :
78                         dec = None
79                 if dec is None :
80                         dec = 'utf_8'
81                 txt = txt[ skip : ]
82                 try :
83                         txt = txt.decode( dec )
84                 except UnicodeDecodeError :
85                         # fallback to ISO-8859-1
86                         txt = txt.decode( 'iso-8859-1' )
87         return txt
88
89 def readDoc( uri ) :
90
91         txt = urllib.urlopen( uri ).read()
92         txt = decodeDocument( txt )
93         return htmltree.parse( txt )
94
95 def resetUserAgent( env ) :
96
97         ua = env.get( 'ua' )
98         if len( ua ) == 1 :
99                 ua = ua[ 0 ]
100                 if isinstance( ua , basestring ) :
101                         urllib.URLopener.version = ua
102
103 def extSerialize( context ) :
104
105         return Sequence( context.item.serialize() )
106
107 def fnDoc( context , uri , _cache = {} ) :
108
109         uri = uri[ 0 ]
110         if uri not in _cache :
111                 try :
112                         doc = readDoc( uri )
113                 except :
114                         raise
115                         print 'Error reading URI %s' % uri
116                         return ()
117                 _cache[ uri ] = Sequence( doc )
118         return _cache[ uri ]
119
120 def evaluate( expr , dot , env , functions ) :
121
122         #
123         # Compile the expression
124         #
125         t = time.time()
126         x = XPath( expr )
127         dp = time.time() - t
128
129         #
130         # Evaluate the compiled expression
131         #
132         t = time.time()
133         r = x.eval( dot , env , functions )
134         de = time.time() - t
135
136         return r , dp , de
137
138 def printResult( sequence , mode , displayLocation ) :
139
140         if mode == 'inline' :
141                 printInlineSequence( sequence )
142         elif mode == 'full' :
143                 printSequence( sequence , True , displayLocation )
144         elif mode == 'short' :
145                 printSequence( sequence , False , displayLocation )
146         else : # assuming default mode
147                 if len( sequence ) == 0 :
148                         pass
149                 elif len( sequence ) == 1 :
150                         print sequence[ 0 ]
151                 else :
152                         print sequence
153
154 help = r'''
155 <expression>          Evaluate XPath expression.
156 $var := <expression>  Evaluate XPath expression and store result in 'var'.
157
158 Commands:
159
160 \. URI         Load document from URI and set it as the current node.
161 \?             Print this help.
162 \c             Flush cache.
163 \d             Default mode.
164 \e EXPRESSION  Display XPath parser result
165 \f             Full mode.
166 \i             Inline mode.
167 \l             Toggle location display in full and short mode.
168 \o             Switch optimization on/off.
169 \s             Short mode.
170 \v             Print name of available variables.
171 \x             Switch timer on/off.
172 '''.strip()
173
174 def main() :
175
176         import readline
177         import re
178
179         reDef = re.compile( r'^\s*\$([a-z_][a-z_0-9-]*)\s*:=\s*(.*)$' , re.I )
180
181         doc = Document()
182
183         mode = 'default'
184
185         modes = {
186                 'd' : 'default' ,
187                 'f' : 'full' ,
188                 's' : 'short' ,
189                 'i' : 'inline'
190                 }
191
192         env = {
193                 'current' : doc ,
194                 'version' : Sequence( 'TX' , '0.1' ) ,
195                 'ua'      : Sequence( g_defaultUserAgent )
196                 }
197
198         functions = {
199                 'doc' : fnDoc ,
200                 'serialize' : extSerialize
201                 }
202
203         print 'XPath TX 0.1 - (c)2005  Frederic Jolliton <frederic@jolliton.com>\n'
204         print 'Use \? for help.\n'
205
206         displayLocation = False
207         showTime = False
208         while 1 :
209                 try :
210                         line = raw_input( 'XPath2.0> ' )
211                 except EOFError :
212                         print
213                         break
214                 except KeyboardInterrupt :
215                         print
216                         break
217                 line = line.strip()
218                 if line.startswith( '#' ) or not line :
219                         pass
220                 elif line.startswith( '\\' ) :
221                         resetUserAgent( env )
222                         cmd = line[ 1 : ]
223                         if cmd in modes :
224                                 mode = modes[ cmd ]
225                                 print '[%s]' % mode
226                         elif cmd.startswith( '. ' ) :
227                                 uri = cmd[ 2 : ].strip()
228                                 try :
229                                         env[ 'current' ] = readDoc( uri )
230                                 except :
231                                         print 'Error reading URI %r' % uri
232                         elif cmd.startswith( 'e ' ) :
233                                 try :
234                                         print lispy( xpathparser.parse( cmd[ 1 : ].strip() ) )
235                                 except NoMatch , e :
236                                         print e
237                         elif cmd == 'c' :
238                                 import xpath
239                                 xpath.flushCache()
240                                 print 'Cache flushed'
241                         elif cmd == 'o' :
242                                 import xpath
243                                 xpath.g_dontOptimize = not xpath.g_dontOptimize
244                                 print 'Optimization turned' , ('off','on')[ not xpath.g_dontOptimize ]
245                         elif cmd == 'l' :
246                                 displayLocation = not displayLocation
247                                 print 'Location' , ('off','on')[ displayLocation ]
248                         elif cmd == 'x' :
249                                 showTime = not showTime
250                                 print 'Timer' , ('off','on')[ showTime ]
251                         elif cmd == 'v' :
252                                 print '$' + ', $'.join( sorted( env.keys() ) )
253                         elif cmd == '?' :
254                                 print help
255                                 print
256                                 print 'Current mode is %r' % mode
257                                 print 'Location is' , ('off','on')[ displayLocation ]
258                                 print 'Timer is' , ('off','on')[ showTime ]
259                         else :
260                                 print 'Unknown command %r' % cmd
261                 else :
262                         r = reDef.match( line )
263                         if r is not None :
264                                 varName , line = r.groups()
265                         else :
266                                 varName = None
267                         resetUserAgent( env )
268                         try :
269                                 dot = env[ 'current' ]
270                                 if isinstance( dot , Sequence ) :
271                                         assert len( dot ) == 1 , 'expected a sequence of 1 item'
272                                         dot = dot[ 0 ]
273                                 try :
274                                         result , dp , de = evaluate( line , dot , env , functions )
275                                 except KeyboardInterrupt :
276                                         print '[Interrupted]'
277                                 else :
278                                         if varName is not None :
279                                                 env[ varName ] = result
280                                         else :
281                                                 try :
282                                                         printResult( result , mode , displayLocation )
283                                                 except KeyboardInterrupt : # FIXME: Don't work.
284                                                         print '[Interrupted]'
285                                         if showTime :
286                                                 print '-- %fs(parse) + %fs(eval) --' % ( dp , de )
287                         except KeyboardInterrupt :
288                                 raise
289                         except NoMatch , e :
290                                 print e
291                         except XPathError , e :
292                                 print 'XPATH-ERROR [%s]' % ( e , )
293                         except Error , e :
294                                 print 'GENERIC-ERROR [%s]' % ( e , )
295         print 'bye'
296
297 if __name__ == '__main__' :
298         main()
299
300 # Local Variables:
301 # tab-width: 4
302 # python-indent: 4
303 # End: