pythonpyqt5qdomdocument

Cannot divide xml elements of one QDomDocument() node and append the elements to the DOM model with pyqt5


I have this code that works so far, to have a direct tree view of an xml file:

from PyQt5.QtCore import QAbstractItemModel, QFile, QIODevice, QModelIndex, Qt
from PyQt5.QtWidgets import QApplication, QFileDialog, QMainWindow, QTreeView
from PyQt5.QtXml import QDomDocument, QDomElement


class DomItem(object):
    def __init__(self, node, row, parent=None):
        self.domNode = node
        # Record the item's location within its parent.
        self.rowNumber = row
        self.parentItem = parent
        self.childItems = {}

    def node(self):
        return self.domNode

    def parent(self):
        return self.parentItem

    def child(self, i):
        if i in self.childItems:
        return self.childItems[i]

    if 0 <= i < self.domNode.childNodes().count():
        childNode = self.domNode.childNodes().item(i)
        childItem = DomItem(childNode, i, self)
        self.childItems[i] = childItem
        return childItem

    return None

    def row(self):
        return self.rowNumber


class DomModel(QAbstractItemModel):
    def __init__(self, document, parent=None):
        super(DomModel, self).__init__(parent)

        self.domDocument = document

        self.rootItem = DomItem(self.domDocument, 0)

    def columnCount(self, parent):
    # return 3
        return 2

    def data(self, index, role):
        if not index.isValid():
            return None

        if role != Qt.DisplayRole:
            return None

        item = index.internalPointer()

        node = item.node()

        if index.column() == 0:
            # print(node.nodeName())
            if node.nodeName() != '#text':
                return node.nodeName()
            else:
                return None

        if index.column() == 1:
            value = node.nodeValue()
            if value is None:
                 return ''

            else:
                return ' '.join(node.nodeValue().split('\n'))

        return None

    def flags(self, index):
        if not index.isValid():
            return Qt.NoItemFlags

        return Qt.ItemIsEnabled | Qt.ItemIsSelectable

    def headerData(self, section, orientation, role):
        if orientation == Qt.Horizontal and role == Qt.DisplayRole:
            if section == 0:
                return "Category"

            if section == 1:
                return "Name"

        return None

    def index(self, row, column, parent):
        if not self.hasIndex(row, column, parent):
            return QModelIndex()

        if not parent.isValid():
            parentItem = self.rootItem
        else:
            parentItem = parent.internalPointer()

        childItem = parentItem.child(row)

        if childItem:
            return self.createIndex(row, column, childItem)
        else:
            return QModelIndex()

    def parent(self, child):
        if not child.isValid():
            return QModelIndex()

        childItem = child.internalPointer()
        parentItem = childItem.parent()

        if not parentItem or parentItem == self.rootItem:
            return QModelIndex()

        return self.createIndex(parentItem.row(), 0, parentItem)

    def rowCount(self, parent):
        if parent.column() > 0:
            return 0

        if not parent.isValid():
            parentItem = self.rootItem
        else:
            parentItem = parent.internalPointer()

            return parentItem.node().childNodes().count()

class MainWindow(QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()

    self.fileMenu = self.menuBar().addMenu("&File")
    self.fileMenu.addAction("&Open...", self.openFile, "Ctrl+O")
    self.fileMenu.addAction("E&xit", self.close, "Ctrl+Q")

    self.xmlPath = ""
    self.model = DomModel(QDomDocument(), self)
    self.view = QTreeView(self)
    self.view.setModel(self.model)

    self.setCentralWidget(self.view)
    self.setWindowTitle("Simple DOM Model")

    def openFile(self, filePath):
        if filePath:
            f = QFile(filePath)
            if f.open(QIODevice.ReadOnly):
                document = QDomDocument()
                if document.setContent(f):
                    newModel = DomModel(document, self)
                    self.view.setModel(newModel)
                    self.view.expandAll()
                    self.model = newModel
                    self.xmlPath = filePath

                f.close()


if __name__ == '__main__':
    import sys

     app = QApplication(sys.argv)
     window = MainWindow()
     window.resize(640, 480)
     window.show()
     window.openFile('your file full path')
     sys.exit(app.exec_())

The xml file is:

<repoCoordinates>
  <name>repostructuretrials</name>
  <branches>master</branches>
  <submodules>
    <submodule_0>
      <name>gameengine</name>
      <branches>*master,remotes/origin/develop,remotes/origin/feature/NormalMappingAndTextureCombination,remotes/origin/feature/light,remotes/origin/feature/particleEmitter,remotes/origin/master</branches>
    </submodule_0>
    <submodule_1>
      <name>GraphicEngineOpenGLBasics</name>
      <branches>feature/shadows,*master,remotes/origin/develop,remotes/origin/feature/billboards,remotes/origin/feature/shadows,remotes/origin/master</branches>
    </submodule_1>
  </submodules>
</repoCoordinates>

And I obtain this: enter image description here

The code is large but not very complex. My problem is that I want to have each branch name as a diferent item in the tree model, and all the values separated by comas are the one unique item for the model. I tried parsing the xml, and played with the QDomElement() class trying to append childs to my nodes to be able to choose each of the branches separatedly.

I would like to split all the branches names by the "," and have this nodes added in my DOM.

Any help on this would me much appreciated, as well as any advice to handle this if the QDomDocument class is not the best way.

Thanks a lot


Solution

  • This is a very basic attempt that could achieve what you're looking for.

    What it does is to create subchildren whenever the node detects its name is "branches", so that the item model can see them as actual children nodes. The children are manually created by writing their own index on the parent childItems dict, so that the model can navigate between the parents.

    Keep in mind that this approach is far from perfect: for instance, the model and the relative DOM is not write-safe (I've not that amount of experience with the QtXml module).

    class DomItem(object):
        # ...
        def child(self, i):
            if i in self.childItems:
                return self.childItems[i]
    
            if 0 <= i < self.domNode.childNodes().count():
                childNode = self.domNode.childNodes().item(i)
                childItem = DomItem(childNode, i, self)
                self.childItems[i] = childItem
                if childNode.nodeName() == 'branches':
                    # based on your example xml there should be only one child entry
                    # for each "branches" node, but, just in case, let's cycle amongst
                    # every possible child node
                    subChildNodes = childNode.childNodes()
                    childIndex = 0
                    for c in range(subChildNodes.count()):
                        subChild = subChildNodes.at(c)
                        for tag in subChild.nodeValue().split(','):
                            branch = childNode.ownerDocument().createElement(tag)
                            childNode.appendChild(branch)
                            branchItem = DomItem(branch, childIndex, childItem)
                            childItem.childItems[childIndex] = branchItem
                            childIndex += 1
                return childItem
    
            return None