338690aff50db2ea09b0cf73c67647a3ecd0b852
[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 sequence import Sequence
31 from nodes import Node, Document
32 from error import Error
33 from parser import NoMatch
34 from sequence_misc import printSequence
35 from xpathfn import *
36
37 from xpath_misc import lispy
38
39 g_defaultUserAgent = 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)'
40
41 def printInlineSequence( sequence ) :
42
43         for item in sequence :
44                 if isinstance( item , Node ) :
45                         item.serialize( file = sys.stdout )
46                         print
47                 else :
48                         print item
49
50 def readDoc( uri ) :
51
52         txt = urllib.urlopen( uri ).read()
53         return htmltree.parse( txt )
54
55 def resetUserAgent( env ) :
56
57         ua = env.get( 'ua' )
58         if len( ua ) == 1 :
59                 ua = ua[ 0 ]
60                 if isinstance( ua , basestring ) :
61                         urllib.URLopener.version = ua
62
63 def extSerialize( context ) :
64
65         return Sequence( context.item.serialize() )
66
67 def fnDoc( context , uri , _cache = {} ) :
68
69         uri = uri[ 0 ]
70         if uri not in _cache :
71                 try :
72                         doc = readDoc( uri )
73                 except :
74                         raise
75                         print 'Error reading URI %s' % uri
76                         return ()
77                 _cache[ uri ] = Sequence( doc )
78         return _cache[ uri ]
79
80 def evaluate( expr , dot , env , functions ) :
81
82         #
83         # Compile the expression
84         #
85         t = time.time()
86         x = XPath( expr )
87         dp = time.time() - t
88
89         #
90         # Evaluate the compiled expression
91         #
92         t = time.time()
93         r = x.eval( dot , env , functions )
94         de = time.time() - t
95
96         return r , dp , de
97
98 def printResult( sequence , mode , displayLocation ) :
99
100         if mode == 'inline' :
101                 printInlineSequence( sequence )
102         elif mode == 'full' :
103                 printSequence( sequence , True , displayLocation )
104         elif mode == 'short' :
105                 printSequence( sequence , False , displayLocation )
106         else : # assuming default mode
107                 if len( sequence ) == 0 :
108                         pass
109                 elif len( sequence ) == 1 :
110                         print sequence[ 0 ]
111                 else :
112                         print sequence
113
114 help = r'''
115 <expression>          Evaluate XPath expression.
116 $var := <expression>  Evaluate XPath expression and store result in 'var'.
117
118 Commands:
119
120 \. URI         Load document from URI and set it as the current node.
121 \?             Print this help.
122 \c             Flush cache.
123 \d             Default mode.
124 \e EXPRESSION  Display XPath parser result
125 \f             Full mode.
126 \i             Inline mode.
127 \l             Toggle location display in full and short mode.
128 \o             Switch optimization on/off.
129 \s             Short mode.
130 \v             Print name of available variables.
131 \x             Switch timer on/off.
132 '''.strip()
133
134 def main() :
135
136         import readline
137         import re
138
139         reDef = re.compile( r'^\s*\$([a-z_][a-z_0-9-]*)\s*:=\s*(.*)$' , re.I )
140
141         doc = Document()
142
143         mode = 'default'
144
145         modes = {
146                 'd' : 'default' ,
147                 'f' : 'full' ,
148                 's' : 'short' ,
149                 'i' : 'inline'
150                 }
151
152         env = {
153                 'current' : doc ,
154                 'version' : Sequence( 'TX' , '0.1' ) ,
155                 'ua'      : Sequence( g_defaultUserAgent )
156                 }
157
158         functions = {
159                 'doc' : fnDoc ,
160                 'serialize' : extSerialize
161                 }
162
163         print 'XPath TX 0.1 - (c)2005  Frederic Jolliton <frederic@jolliton.com>\n'
164         print 'Use \? for help.\n'
165
166         displayLocation = False
167         showTime = False
168         while 1 :
169                 try :
170                         line = raw_input( 'XPath2.0> ' )
171                 except EOFError :
172                         print
173                         break
174                 except KeyboardInterrupt :
175                         print
176                         break
177                 try :
178                         line = line.decode( sys.stdin.encoding )
179                 except :
180                         print 'Error decoding input with encoding %r. Using raw input.' % ( sys.stdin.encoding , )
181                 line = line.strip()
182                 if line.startswith( '#' ) or not line :
183                         pass
184                 elif line.startswith( '\\' ) :
185                         resetUserAgent( env )
186                         cmd = line[ 1 : ]
187                         if cmd in modes :
188                                 mode = modes[ cmd ]
189                                 print '[%s]' % mode
190                         elif cmd.startswith( '. ' ) :
191                                 uri = cmd[ 2 : ].strip()
192                                 try :
193                                         env[ 'current' ] = readDoc( uri )
194                                 except :
195                                         print 'Error reading URI %r' % uri
196                         elif cmd.startswith( 'e ' ) :
197                                 try :
198                                         print lispy( xpathparser.parse( cmd[ 1 : ].strip() ) )
199                                 except NoMatch , e :
200                                         print e
201                         elif cmd == 'c' :
202                                 import xpath
203                                 xpath.flushCache()
204                                 print 'Cache flushed'
205                         elif cmd == 'o' :
206                                 import xpath
207                                 xpath.g_dontOptimize = not xpath.g_dontOptimize
208                                 print 'Optimization turned' , ('off','on')[ not xpath.g_dontOptimize ]
209                         elif cmd == 'l' :
210                                 displayLocation = not displayLocation
211                                 print 'Location' , ('off','on')[ displayLocation ]
212                         elif cmd == 'x' :
213                                 showTime = not showTime
214                                 print 'Timer' , ('off','on')[ showTime ]
215                         elif cmd == 'v' :
216                                 print '$' + ', $'.join( sorted( env.keys() ) )
217                         elif cmd == '?' :
218                                 print help
219                                 print
220                                 print 'Current mode is %r' % mode
221                                 print 'Location is' , ('off','on')[ displayLocation ]
222                                 print 'Timer is' , ('off','on')[ showTime ]
223                         else :
224                                 print 'Unknown command %r' % cmd
225                 else :
226                         r = reDef.match( line )
227                         if r is not None :
228                                 varName , line = r.groups()
229                         else :
230                                 varName = None
231                         resetUserAgent( env )
232                         try :
233                                 dot = env[ 'current' ]
234                                 if isinstance( dot , Sequence ) :
235                                         assert len( dot ) == 1 , 'expected a sequence of 1 item'
236                                         dot = dot[ 0 ]
237                                 try :
238                                         result , dp , de = evaluate( line , dot , env , functions )
239                                 except KeyboardInterrupt :
240                                         print '[Interrupted]'
241                                 else :
242                                         if varName is not None :
243                                                 env[ varName ] = result
244                                         else :
245                                                 try :
246                                                         printResult( result , mode , displayLocation )
247                                                 except KeyboardInterrupt : # FIXME: Don't work.
248                                                         print '[Interrupted]'
249                                         if showTime :
250                                                 print '-- %fs(parse) + %fs(eval) --' % ( dp , de )
251                         except KeyboardInterrupt :
252                                 raise
253                         except NoMatch , e :
254                                 print e
255                         except XPathError , e :
256                                 print 'XPATH-ERROR [%s]' % ( e , )
257                         except Error , e :
258                                 print 'GENERIC-ERROR [%s]' % ( e , )
259         print 'bye'
260
261 if __name__ == '__main__' :
262         main()
263
264 # Local Variables:
265 # tab-width: 4
266 # python-indent: 4
267 # End: