3 from PyQt4.QtXml import QDomDocument
4 from sface.xmlwidget import XmlWindow, DomModel, XmlView, XmlDelegate, ElemNodeDelegate, TextNodeDelegate, CommentNodeDelegate
6 from PyQt4.QtCore import *
7 from PyQt4.QtGui import *
15 def store(self, rawOutput):
16 self.rawOutput = rawOutput
18 def paramString(self, item):
20 Convert an xmlrpc <PARAM> into a human-readable string
21 Is there an easier way?
23 if item.tag == "string":
25 elif item.tag == "array":
26 arrayValues = item.xpath("data/value")
28 children = list(arrayValues[0])
30 return self.paramString(children[0]) + ", ..."
35 elif item.tag == "struct":
37 members = item.xpath("member")
38 for member in members:
39 names = member.xpath("name")
40 values = member.xpath("value")
41 if (names) and (values):
44 if len(list(value))>0:
46 dict[name.text] = self.paramString(list(value)[0])
51 def parseMethodCall(self, mc):
57 tree = etree.fromstring(mc)
58 except etree.XMLSyntaxError, e:
61 if tree.tag != "methodCall" or (len(list(tree))==0):
64 methodNames = tree.xpath("//methodCall/methodName")
66 if len(methodNames) == 0:
71 params = tree.xpath("//methodCall/params/param/value")
73 children = list(param)
75 request["args"].append(self.paramString(children[0]))
77 request["methodName"] = methodNames[0].text
81 def parseMethodResponse(self, mr):
82 mr = str(mr) # PyQT supplies a QByteArray; make it a string
84 response = {"kind": "unknown"}
87 tree = etree.fromstring(mr)
88 except etree.XMLSyntaxError, e:
89 print "failed to parse XML response", str(e)
90 #file("badparse.xml","w").write(mr)
93 if tree.tag != "methodResponse" or (len(list(tree))==0):
96 # a fault should look like:
99 # faultString: "Register: Missing authority..."
101 faults = tree.xpath("//methodResponse/fault")
103 response["kind"] = "fault"
104 structs = fault.xpath("value/struct")
105 for struct in structs:
106 members = struct.xpath("member")
107 for member in members:
108 names = member.xpath("name")
109 values = member.xpath("value")
110 if (names) and (values):
113 if len(list(value))>0:
114 data = list(value)[0]
115 response[name.text] = data.text
116 # once we have the first fault, return
119 # whatever didn't fault must have succeeded?
120 # "success" really isn't quite the right word. The best we can say is
121 # that the call was executed. The actual op may or may not have
123 response["kind"] = "executed"
127 def extractXml(self):
128 pttrnAsk = '<methodCall>.*?</methodCall>'
129 pttrnAns = '<methodResponse>.*?</methodResponse>'
130 requests = re.compile(pttrnAsk, re.DOTALL).findall(self.rawOutput)
131 replies = re.compile(pttrnAns, re.DOTALL).findall(self.rawOutput)
133 requests = [ x.replace('\\n','\n') for x in requests ]
134 replies = [ x.replace('\\n','\n').replace("'\nbody: '", '').replace("\"\nbody: '", '') for x in replies ]
135 # A well-formed XML document must have one, and only one, top-level element
140 for requestXml in requests:
141 self.xml += requestXml
142 callDict = {"request": requestXml}
143 # we could have less responses than calls, so guard the pop
145 replyXml = replies.pop(0)
148 # parse the response to extract fault information
149 responseDict = self.parseMethodResponse(replyXml)
150 self.responses.append(responseDict)
152 requestDict = self.parseMethodCall(requestXml)
154 callDict["reply"] = replyXml
155 callDict.update(responseDict)
156 callDict.update(requestDict)
158 self.calls.append(callDict)
160 # just in case somehow we ended up with more responses than calls
162 replyXml = replies.pop()
164 self.responses.append(self.parseMethodResponse(replyXml))
169 # statistics: round-trip time, size of the com
172 class XmlrpcTracker(QWidget):
173 def __init__(self, parent=None):
174 QWidget.__init__(self, parent=parent)
176 self.reader = XmlrpcReader()
178 labelCalls = QLabel("Calls: (from most recent to least recent)")
180 self.callTable = QTableWidget(self)
181 self.callTable.setSelectionBehavior(QAbstractItemView.SelectRows)
182 self.callTable.setSelectionMode(QAbstractItemView.SingleSelection)
183 self.callTable.setMaximumHeight(140)
185 labelArgs = QLabel("Params:")
187 self.argsTable = QTableWidget(self)
188 self.argsTable.setMaximumHeight(140)
190 labelXml = QLabel("XMLRPC Contents")
191 self.xmlView = XmlView(self)
193 self.delegate = XmlDelegate(self)
194 self.delegate.insertNodeDelegate('element', ElemNodeDelegate())
195 self.delegate.insertNodeDelegate('text', TextNodeDelegate())
196 self.delegate.insertNodeDelegate('comment', CommentNodeDelegate())
197 self.xmlView.setItemDelegate(self.delegate)
199 self.layout = QVBoxLayout()
200 self.layout.addWidget(labelCalls)
201 self.layout.addWidget(self.callTable)
202 self.layout.addWidget(labelArgs)
203 self.layout.addWidget(self.argsTable)
204 self.layout.addWidget(labelXml)
205 self.layout.addWidget(self.xmlView)
207 self.setLayout(self.layout)
209 self.connect(self.callTable, SIGNAL("itemSelectionChanged ()"), self.onCallSelect)
211 def getAndPrint(self, rawOutput):
212 self.reader.store(rawOutput)
213 self.reader.extractXml()
214 self.updateCallTable()
216 def updateCallTable(self):
217 self.callTable.clear()
218 self.callTable.setColumnCount(2)
219 self.callTable.setHorizontalHeaderLabels(["name", "status"])
221 calls = self.reader.calls
222 self.callTable.setRowCount(len(calls))
225 reverse_calls = list(enumerate(calls))
226 reverse_calls.reverse()
227 for (index,call) in reverse_calls:
228 item = QTableWidgetItem(call.get("methodName", "unknown"))
229 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
230 item.setData(Qt.UserRole, index)
231 self.callTable.setItem(row, 0, item)
233 item = QTableWidgetItem(call.get("kind", "unknown"))
234 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
235 item.setData(Qt.UserRole, index)
236 self.callTable.setItem(row, 1, item)
240 self.callTable.resizeColumnsToContents()
242 def updateArgsTable(self, args):
243 self.argsTable.clear()
244 self.argsTable.setColumnCount(1)
245 self.argsTable.setHorizontalHeaderLabels(["param"])
246 self.argsTable.horizontalHeader().hide()
247 self.argsTable.setHorizontalScrollMode(QAbstractItemView.ScrollPerPixel)
249 self.argsTable.setRowCount(len(args))
254 arg = arg[:255] + "..."
256 item = QTableWidgetItem(arg)
257 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
258 self.argsTable.setItem(row, 0, item)
262 self.argsTable.resizeColumnsToContents()
264 def onCallSelect(self):
265 selItems = self.callTable.selectedItems()
266 if (len(selItems) <= 0):
269 row = selItems[0].data(Qt.UserRole).toInt()[0]
271 calls = self.reader.calls
277 xml = "<debug>" + call.get("request","") + call.get("reply", "") + "</debug>"
278 #xml = call.get("request","") + call.get("reply", "")
282 self.updateArgsTable(call.get("args",[]))
284 def displayXml(self, xml):
285 self.document = QDomDocument("XMLRPC Tracker")
286 self.document.setContent(xml)
287 self.model = DomModel(self.document, self)
288 self.xmlView.setModel(self.model)
291 # There's one global tracker for XMLRPCs. Use init_tracker() to create it, and
292 # get_tracker() to get it.
296 def init_tracker(parent):
299 glo_tracker = XmlrpcTracker(parent)