1#!/usr/bin/env python
2
3
4#############################################################################
5##
6## Copyright (C) 2013 Riverbank Computing Limited.
7## Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
8## All rights reserved.
9##
10## This file is part of the examples of PyQt.
11##
12## $QT_BEGIN_LICENSE:BSD$
13## You may use this file under the terms of the BSD license as follows:
14##
15## "Redistribution and use in source and binary forms, with or without
16## modification, are permitted provided that the following conditions are
17## met:
18##   * Redistributions of source code must retain the above copyright
19##     notice, this list of conditions and the following disclaimer.
20##   * Redistributions in binary form must reproduce the above copyright
21##     notice, this list of conditions and the following disclaimer in
22##     the documentation and/or other materials provided with the
23##     distribution.
24##   * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
25##     the names of its contributors may be used to endorse or promote
26##     products derived from this software without specific prior written
27##     permission.
28##
29## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
30## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
31## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
32## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
33## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
34## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
35## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
36## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
37## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
38## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
39## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
40## $QT_END_LICENSE$
41##
42#############################################################################
43
44
45from PyQt5.QtCore import QAbstractItemModel, QFile, QIODevice, QModelIndex, Qt
46from PyQt5.QtWidgets import QApplication, QFileDialog, QMainWindow, QTreeView
47from PyQt5.QtXml import QDomDocument
48
49
50class DomItem(object):
51    def __init__(self, node, row, parent=None):
52        self.domNode = node
53        # Record the item's location within its parent.
54        self.rowNumber = row
55        self.parentItem = parent
56        self.childItems = {}
57
58    def node(self):
59        return self.domNode
60
61    def parent(self):
62        return self.parentItem
63
64    def child(self, i):
65        if i in self.childItems:
66            return self.childItems[i]
67
68        if i >= 0 and i < self.domNode.childNodes().count():
69            childNode = self.domNode.childNodes().item(i)
70            childItem = DomItem(childNode, i, self)
71            self.childItems[i] = childItem
72            return childItem
73
74        return None
75
76    def row(self):
77        return self.rowNumber
78
79
80class DomModel(QAbstractItemModel):
81    def __init__(self, document, parent=None):
82        super(DomModel, self).__init__(parent)
83
84        self.domDocument = document
85
86        self.rootItem = DomItem(self.domDocument, 0)
87
88    def columnCount(self, parent):
89        return 3
90
91    def data(self, index, role):
92        if not index.isValid():
93            return None
94
95        if role != Qt.DisplayRole:
96            return None
97
98        item = index.internalPointer()
99
100        node = item.node()
101        attributes = []
102        attributeMap = node.attributes()
103
104        if index.column() == 0:
105            return node.nodeName()
106
107        elif index.column() == 1:
108            for i in range(0, attributeMap.count()):
109                attribute = attributeMap.item(i)
110                attributes.append(attribute.nodeName() + '="' +
111                                  attribute.nodeValue() + '"')
112
113            return " ".join(attributes)
114
115        if index.column() == 2:
116            value = node.nodeValue()
117            if value is None:
118                return ''
119
120            return ' '.join(node.nodeValue().split('\n'))
121
122        return None
123
124    def flags(self, index):
125        if not index.isValid():
126            return Qt.NoItemFlags
127
128        return Qt.ItemIsEnabled | Qt.ItemIsSelectable
129
130    def headerData(self, section, orientation, role):
131        if orientation == Qt.Horizontal and role == Qt.DisplayRole:
132            if section == 0:
133                return "Name"
134
135            if section == 1:
136                return "Attributes"
137
138            if section == 2:
139                return "Value"
140
141        return None
142
143    def index(self, row, column, parent):
144        if not self.hasIndex(row, column, parent):
145            return QModelIndex()
146
147        if not parent.isValid():
148            parentItem = self.rootItem
149        else:
150            parentItem = parent.internalPointer()
151
152        childItem = parentItem.child(row)
153        if childItem:
154            return self.createIndex(row, column, childItem)
155        else:
156            return QModelIndex()
157
158    def parent(self, child):
159        if not child.isValid():
160            return QModelIndex()
161
162        childItem = child.internalPointer()
163        parentItem = childItem.parent()
164
165        if not parentItem or parentItem == self.rootItem:
166            return QModelIndex()
167
168        return self.createIndex(parentItem.row(), 0, parentItem)
169
170    def rowCount(self, parent):
171        if parent.column() > 0:
172            return 0
173
174        if not parent.isValid():
175            parentItem = self.rootItem
176        else:
177            parentItem = parent.internalPointer()
178
179        return parentItem.node().childNodes().count()
180
181
182class MainWindow(QMainWindow):
183    def __init__(self):
184        super(MainWindow, self).__init__()
185
186        self.fileMenu = self.menuBar().addMenu("&File")
187        self.fileMenu.addAction("&Open...", self.openFile, "Ctrl+O")
188        self.fileMenu.addAction("E&xit", self.close, "Ctrl+Q")
189
190        self.xmlPath = ""
191        self.model = DomModel(QDomDocument(), self)
192        self.view = QTreeView(self)
193        self.view.setModel(self.model)
194
195        self.setCentralWidget(self.view)
196        self.setWindowTitle("Simple DOM Model")
197
198    def openFile(self):
199        filePath, _ = QFileDialog.getOpenFileName(self, "Open File",
200                self.xmlPath, "XML files (*.xml);;HTML files (*.html);;"
201                "SVG files (*.svg);;User Interface files (*.ui)")
202
203        if filePath:
204            f = QFile(filePath)
205            if f.open(QIODevice.ReadOnly):
206                document = QDomDocument()
207                if document.setContent(f):
208                    newModel = DomModel(document, self)
209                    self.view.setModel(newModel)
210                    self.model = newModel
211                    self.xmlPath = filePath
212
213                f.close()
214
215
216if __name__ == '__main__':
217
218    import sys
219
220    app = QApplication(sys.argv)
221    window = MainWindow()
222    window.resize(640, 480)
223    window.show()
224    sys.exit(app.exec_())
225