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