如何将xml文件拖放到Qtableview中同时解析xml
How to drag a xml file and drop it in Qtableview and parse the xml at the same time
我想在QtableView或QtableWidget中拖拽一个XML文件,同时解析文件,在QtableView的不同列中显示。
我已经看过很多示例,但不确定从哪里开始,是否有任何教程或示例,我需要在 PyQt5 中完成。
我是第一次这样做,不确定如何开始。
我有一个非常简单的 XML 文件示例:每个图层名称是第一列,然后子标签是相同的相应属性。
<xtech>
<Layer id="0" name="EM_UPKG">
<SourceLayer>NoSource</SourceLayer>
<Color>
<R>255</R>
<G>255</G>
<B>0</B>
</Color>
<Offset>
<value>114.215</value>
<dependent>EM_AlN</dependent>
<placement>Top</placement>
</Offset>
<Thickness>
<value>50</value>
<dependent>-1</dependent>
</Thickness>
<Material>air</Material>
<Port>NO</Port>
<Resistivity>0</Resistivity>
<Transparency>0.8</Transparency>
<Sheet_Priority>0</Sheet_Priority>
</Layer>
<Layer id="1" name="EM_UP">
<SourceLayer>NoSource</SourceLayer>
<Color>
<R>255</R>
<G>128</G>
<B>0</B>
</Color>
<Offset>
<value>164.215</value>
<dependent>EM_UVIA</dependent>
<placement>Top</placement>
</Offset>
<Thickness>
<value>40</value>
<dependent>-1</dependent>
</Thickness>
<Material>copper</Material>
<Port>NO</Port>
<Resistivity>0</Resistivity>
<Transparency>0</Transparency>
<Sheet_Priority>0</Sheet_Priority>
</Layer>
<Layer id="2" name="EM_UVIA">
<SourceLayer>NoSource</SourceLayer>
<Color>
<R>128</R>
<G>128</G>
<B>0</B>
</Color>
<Offset>
<value>114.215</value>
<dependent>EM_AlN</dependent>
<placement>Top</placement>
</Offset>
<Thickness>
<value>50</value>
<dependent>-1</dependent>
</Thickness>
<Material>copper</Material>
<Port>NO</Port>
<Resistivity>0</Resistivity>
<Transparency>0</Transparency>
<Sheet_Priority>0</Sheet_Priority>
</Layer>
<Layer id="3" name="EM_PKG">
<SourceLayer>NoSource</SourceLayer>
<Color>
<R>255</R>
<G>255</G>
<B>0</B>
</Color>
<Offset>
<value>114.215</value>
<dependent>EM_AlN</dependent>
<placement>Top</placement>
</Offset>
<Thickness>
<value>20</value>
<dependent>-1</dependent>
</Thickness>
<Material>air</Material>
<Port>NO</Port>
<Resistivity>0</Resistivity>
<Transparency>0.8</Transparency>
<Sheet_Priority>0</Sheet_Priority>
</Layer>
<Layer id="4" name="EM_PL">
<SourceLayer>NoSource</SourceLayer>
<Color>
<R>255</R>
<G>128</G>
<B>0</B>
</Color>
<Offset>
<value>114.215</value>
<dependent>EM_AlN</dependent>
<placement>Top</placement>
</Offset>
<Thickness>
<value>95</value>
<dependent>-1</dependent>
</Thickness>
<Material>copper</Material>
<Port>NO</Port>
<Resistivity>0</Resistivity>
<Transparency>0</Transparency>
<Sheet_Priority>0</Sheet_Priority>
</Layer>
<Mesh_Operations>
<Model_Resolution>60</Model_Resolution>
<Surface_Approximation>0.5</Surface_Approximation>
</Mesh_Operations>
<Port_Processing>
<Use_Delta>True</Use_Delta>
<hport_delta>5</hport_delta>
</Port_Processing>
</xtech>
列应显示为:
目标层、颜色、偏移、厚度、MAterial、端口、Sheet电阻率、透明度、优先级和源层
所以我写了下面的代码来实现拖放,现在我必须研究如何解析 XML 文件。
def __init__(self,parent=None):
super().__init__()
self.ui = Ui_MainWindowEtechEditor()
self.ui.setupUi(self)
self.setAcceptDrops(True)
self.ui.pushButton.clicked.connect(self.opecolorEditor)
def opecolorEditor(self):
color = QColorDialog(self)
color.setSizeGripEnabled(True)
color.layout().setSizeConstraint(QLayout.SetNoConstraint)
color.show()
def dragEnterEvent(self, e):
if e.mimeData().hasUrls:
e.accept()
else:
e.ignore()
def dragMoveEvent(self, e):
if e.mimeData().hasUrls:
e.accept()
else:
e.ignore()
def dropEvent(self, e, event=None):
if e.mimeData().hasUrls():
e.setDropAction(QtCore.Qt.CopyAction)
e.accept()
drop_list = []
for url in e.mimeData().urls():
fName = (str(url.toLocalFile()))
print("path ", fName)
self.loadExtechfile(fName)
else:
e.ignore()
Qt 提供 QXmlStreamReader,一个 Xml 解析器。
解析器一次读取一个元素,保持当前元素类型和内容,所以唯一的问题是了解如何跟踪嵌套元素,只要源格式正确。
readNext()
returns xml 元素的元素类型 ("token")(也可以通过 tokenType()
访问)。
最重要的标记类型是 StartElement
和 EndElement
,您可以使用 QXmlStreamReader.attributes().value(attrName)
来读取 StartElement 的属性,并使用 xml.readElementText()
来读取它的内容,这可能是它的文本内容甚至是子元素的原始文本,只要 QXmlStreamReader.IncludeChildElements
作为最后一个参数提供。
Columns = ('Target Layer', 'Color', 'Offset', 'Thickness', 'Material',
'Port', 'Resistivity', 'Transparency', 'Sheet_Priority', 'SourceLayer')
SimpleFields = ('SourceLayer', 'Material', 'Resistivity', 'Port',
'Transparency', 'Sheet_Priority')
class Table(QtWidgets.QTableView):
def __init__(self, *args, **kwargs):
QtWidgets.QTableView.__init__(self, *args, **kwargs)
self.setAcceptDrops(True)
model = QtGui.QStandardItemModel(0, len(Columns))
model.setHorizontalHeaderLabels(Columns)
self.setModel(model)
def dragEnterEvent(self, e):
# be careful, as you forgot the parenthesis of hasUrls()!
if e.mimeData().hasUrls() and any(u.isLocalFile() for u in e.mimeData().urls()):
e.accept()
else:
e.ignore()
# ...
def dropEvent(self, e):
f = QtCore.QFile(e.mimeData().urls()[0].toLocalFile())
if not f.open(f.ReadOnly):
return
xml = QtCore.QXmlStreamReader(f)
valid = False
currentLayer = None
while not xml.atEnd():
if xml.readNext() == xml.StartElement:
# track the first start element and ensure that it's of
# the right type, otherwise ignore the file
if not valid:
if xml.name() != 'xtech':
break
valid = True
continue
if xml.name() == 'Layer':
# if a currentLayer exists, we assume it's finished,
# so we can process it and add its items
if currentLayer:
self.addLayer(currentLayer)
currentLayer = {'Target Layer': xml.attributes().value('name')}
elif xml.name() in SimpleFields:
currentLayer[xml.name()] = xml.readElementText()
elif xml.name() == 'Color':
currentLayer['Color'] = color = QtGui.QColor()
# this is a "nested" field, read its contents until
# it's reached the end element;
while xml.readNext():
if xml.tokenType() == xml.EndElement and xml.name() == 'Color':
break
elif xml.name() == 'R':
color.setRed(int(xml.readElementText()))
elif xml.name() == 'G':
color.setGreen(int(xml.readElementText()))
elif xml.name() == 'B':
color.setBlue(int(xml.readElementText()))
# use the same logic for all other nested fields.
f.close()
def addLayer(self, layer):
# create an empty "row" that can be filled with items in the correct
# order, and that will leave empty cells if some fields do not exist
row = [None] * len(Columns)
for field, value in layer.items():
item = QtGui.QStandardItem()
if isinstance(value, QtGui.QColor):
item.setBackground(value)
else:
item.setData(value, QtCore.Qt.DisplayRole)
row[Columns.index(field)] = item
self.model().appendRow(row)
在解析元素时需要注意字母的大小写,您可能应该使用 xml.name().lower()
来与字段名称进行比较。
经过几个小时的努力,我找到了另一个解析器 xml.etree.ElementTree 并实现了如下所示的解析器代码,它非常有用。
def loadExtechfile(self,fName):
print("load file from ", fName)
tree = ET.parse(fName)
root = tree.getroot()
for layer in root.findall('Layer'):
layerName =layer.get('name')
sourceLayer = layer.find('SourceLayer').text
for color in layer.findall('Color'):
r = color.find('R').text
g = color.find('G').text
b = color.find('B').text
combinedRGB = r+","+g+","+b
for offset in layer.findall('Offset'):
value = offset.find('value').text
dep = offset.find('dependent').text
plc = offset.find('placement').text
if dep == "-1" and plc == "None":
combinedOffset = value
else:
combinedOffset = value + "(= " + dep + " [" + plc + "])"
for thickness in layer.findall('Thickness'):
valueT = thickness.find('value').text
depT = thickness.find('dependent').text
if depT != "-1":
combinedThickness = valueT + "(=" + depT + ")"
else:
combinedThickness = valueT
material = layer.find('Material').text
port = layer.find('Port').text
res = layer.find('Resistivity').text
trans = layer.find('Transparency').text
sheetPrio = layer.find('Sheet_Priority').text
#print(layerName,r,g,b,sourceLayer,material,port,res,trans,sheetPrio)
我想在QtableView或QtableWidget中拖拽一个XML文件,同时解析文件,在QtableView的不同列中显示。
我已经看过很多示例,但不确定从哪里开始,是否有任何教程或示例,我需要在 PyQt5 中完成。
我是第一次这样做,不确定如何开始。
我有一个非常简单的 XML 文件示例:每个图层名称是第一列,然后子标签是相同的相应属性。
<xtech>
<Layer id="0" name="EM_UPKG">
<SourceLayer>NoSource</SourceLayer>
<Color>
<R>255</R>
<G>255</G>
<B>0</B>
</Color>
<Offset>
<value>114.215</value>
<dependent>EM_AlN</dependent>
<placement>Top</placement>
</Offset>
<Thickness>
<value>50</value>
<dependent>-1</dependent>
</Thickness>
<Material>air</Material>
<Port>NO</Port>
<Resistivity>0</Resistivity>
<Transparency>0.8</Transparency>
<Sheet_Priority>0</Sheet_Priority>
</Layer>
<Layer id="1" name="EM_UP">
<SourceLayer>NoSource</SourceLayer>
<Color>
<R>255</R>
<G>128</G>
<B>0</B>
</Color>
<Offset>
<value>164.215</value>
<dependent>EM_UVIA</dependent>
<placement>Top</placement>
</Offset>
<Thickness>
<value>40</value>
<dependent>-1</dependent>
</Thickness>
<Material>copper</Material>
<Port>NO</Port>
<Resistivity>0</Resistivity>
<Transparency>0</Transparency>
<Sheet_Priority>0</Sheet_Priority>
</Layer>
<Layer id="2" name="EM_UVIA">
<SourceLayer>NoSource</SourceLayer>
<Color>
<R>128</R>
<G>128</G>
<B>0</B>
</Color>
<Offset>
<value>114.215</value>
<dependent>EM_AlN</dependent>
<placement>Top</placement>
</Offset>
<Thickness>
<value>50</value>
<dependent>-1</dependent>
</Thickness>
<Material>copper</Material>
<Port>NO</Port>
<Resistivity>0</Resistivity>
<Transparency>0</Transparency>
<Sheet_Priority>0</Sheet_Priority>
</Layer>
<Layer id="3" name="EM_PKG">
<SourceLayer>NoSource</SourceLayer>
<Color>
<R>255</R>
<G>255</G>
<B>0</B>
</Color>
<Offset>
<value>114.215</value>
<dependent>EM_AlN</dependent>
<placement>Top</placement>
</Offset>
<Thickness>
<value>20</value>
<dependent>-1</dependent>
</Thickness>
<Material>air</Material>
<Port>NO</Port>
<Resistivity>0</Resistivity>
<Transparency>0.8</Transparency>
<Sheet_Priority>0</Sheet_Priority>
</Layer>
<Layer id="4" name="EM_PL">
<SourceLayer>NoSource</SourceLayer>
<Color>
<R>255</R>
<G>128</G>
<B>0</B>
</Color>
<Offset>
<value>114.215</value>
<dependent>EM_AlN</dependent>
<placement>Top</placement>
</Offset>
<Thickness>
<value>95</value>
<dependent>-1</dependent>
</Thickness>
<Material>copper</Material>
<Port>NO</Port>
<Resistivity>0</Resistivity>
<Transparency>0</Transparency>
<Sheet_Priority>0</Sheet_Priority>
</Layer>
<Mesh_Operations>
<Model_Resolution>60</Model_Resolution>
<Surface_Approximation>0.5</Surface_Approximation>
</Mesh_Operations>
<Port_Processing>
<Use_Delta>True</Use_Delta>
<hport_delta>5</hport_delta>
</Port_Processing>
</xtech>
列应显示为:
目标层、颜色、偏移、厚度、MAterial、端口、Sheet电阻率、透明度、优先级和源层
所以我写了下面的代码来实现拖放,现在我必须研究如何解析 XML 文件。
def __init__(self,parent=None):
super().__init__()
self.ui = Ui_MainWindowEtechEditor()
self.ui.setupUi(self)
self.setAcceptDrops(True)
self.ui.pushButton.clicked.connect(self.opecolorEditor)
def opecolorEditor(self):
color = QColorDialog(self)
color.setSizeGripEnabled(True)
color.layout().setSizeConstraint(QLayout.SetNoConstraint)
color.show()
def dragEnterEvent(self, e):
if e.mimeData().hasUrls:
e.accept()
else:
e.ignore()
def dragMoveEvent(self, e):
if e.mimeData().hasUrls:
e.accept()
else:
e.ignore()
def dropEvent(self, e, event=None):
if e.mimeData().hasUrls():
e.setDropAction(QtCore.Qt.CopyAction)
e.accept()
drop_list = []
for url in e.mimeData().urls():
fName = (str(url.toLocalFile()))
print("path ", fName)
self.loadExtechfile(fName)
else:
e.ignore()
Qt 提供 QXmlStreamReader,一个 Xml 解析器。
解析器一次读取一个元素,保持当前元素类型和内容,所以唯一的问题是了解如何跟踪嵌套元素,只要源格式正确。
readNext()
returns xml 元素的元素类型 ("token")(也可以通过 tokenType()
访问)。
最重要的标记类型是 StartElement
和 EndElement
,您可以使用 QXmlStreamReader.attributes().value(attrName)
来读取 StartElement 的属性,并使用 xml.readElementText()
来读取它的内容,这可能是它的文本内容甚至是子元素的原始文本,只要 QXmlStreamReader.IncludeChildElements
作为最后一个参数提供。
Columns = ('Target Layer', 'Color', 'Offset', 'Thickness', 'Material',
'Port', 'Resistivity', 'Transparency', 'Sheet_Priority', 'SourceLayer')
SimpleFields = ('SourceLayer', 'Material', 'Resistivity', 'Port',
'Transparency', 'Sheet_Priority')
class Table(QtWidgets.QTableView):
def __init__(self, *args, **kwargs):
QtWidgets.QTableView.__init__(self, *args, **kwargs)
self.setAcceptDrops(True)
model = QtGui.QStandardItemModel(0, len(Columns))
model.setHorizontalHeaderLabels(Columns)
self.setModel(model)
def dragEnterEvent(self, e):
# be careful, as you forgot the parenthesis of hasUrls()!
if e.mimeData().hasUrls() and any(u.isLocalFile() for u in e.mimeData().urls()):
e.accept()
else:
e.ignore()
# ...
def dropEvent(self, e):
f = QtCore.QFile(e.mimeData().urls()[0].toLocalFile())
if not f.open(f.ReadOnly):
return
xml = QtCore.QXmlStreamReader(f)
valid = False
currentLayer = None
while not xml.atEnd():
if xml.readNext() == xml.StartElement:
# track the first start element and ensure that it's of
# the right type, otherwise ignore the file
if not valid:
if xml.name() != 'xtech':
break
valid = True
continue
if xml.name() == 'Layer':
# if a currentLayer exists, we assume it's finished,
# so we can process it and add its items
if currentLayer:
self.addLayer(currentLayer)
currentLayer = {'Target Layer': xml.attributes().value('name')}
elif xml.name() in SimpleFields:
currentLayer[xml.name()] = xml.readElementText()
elif xml.name() == 'Color':
currentLayer['Color'] = color = QtGui.QColor()
# this is a "nested" field, read its contents until
# it's reached the end element;
while xml.readNext():
if xml.tokenType() == xml.EndElement and xml.name() == 'Color':
break
elif xml.name() == 'R':
color.setRed(int(xml.readElementText()))
elif xml.name() == 'G':
color.setGreen(int(xml.readElementText()))
elif xml.name() == 'B':
color.setBlue(int(xml.readElementText()))
# use the same logic for all other nested fields.
f.close()
def addLayer(self, layer):
# create an empty "row" that can be filled with items in the correct
# order, and that will leave empty cells if some fields do not exist
row = [None] * len(Columns)
for field, value in layer.items():
item = QtGui.QStandardItem()
if isinstance(value, QtGui.QColor):
item.setBackground(value)
else:
item.setData(value, QtCore.Qt.DisplayRole)
row[Columns.index(field)] = item
self.model().appendRow(row)
在解析元素时需要注意字母的大小写,您可能应该使用 xml.name().lower()
来与字段名称进行比较。
经过几个小时的努力,我找到了另一个解析器 xml.etree.ElementTree 并实现了如下所示的解析器代码,它非常有用。
def loadExtechfile(self,fName):
print("load file from ", fName)
tree = ET.parse(fName)
root = tree.getroot()
for layer in root.findall('Layer'):
layerName =layer.get('name')
sourceLayer = layer.find('SourceLayer').text
for color in layer.findall('Color'):
r = color.find('R').text
g = color.find('G').text
b = color.find('B').text
combinedRGB = r+","+g+","+b
for offset in layer.findall('Offset'):
value = offset.find('value').text
dep = offset.find('dependent').text
plc = offset.find('placement').text
if dep == "-1" and plc == "None":
combinedOffset = value
else:
combinedOffset = value + "(= " + dep + " [" + plc + "])"
for thickness in layer.findall('Thickness'):
valueT = thickness.find('value').text
depT = thickness.find('dependent').text
if depT != "-1":
combinedThickness = valueT + "(=" + depT + ")"
else:
combinedThickness = valueT
material = layer.find('Material').text
port = layer.find('Port').text
res = layer.find('Resistivity').text
trans = layer.find('Transparency').text
sheetPrio = layer.find('Sheet_Priority').text
#print(layerName,r,g,b,sourceLayer,material,port,res,trans,sheetPrio)