Search code examples
pythonxpathpyside2qtxml

Parsing the XML (Collada/Dae) file using XPath


I want to rewrite my example from Qt C++ to Python.

Input file ("Plane.dae")

<?xml version="1.0" encoding="utf-8"?>
<COLLADA xmlns="http://www.collada.org/2005/11/COLLADASchema" version="1.4.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <asset>
    <contributor>
      <author>Blender User</author>
      <authoring_tool>Blender 2.83.3 commit date:2020-07-22, commit time:06:01, hash:353e5bd7493e</authoring_tool>
    </contributor>
    <created>2020-08-03T14:03:19</created>
    <modified>2020-08-03T14:03:19</modified>
    <unit name="meter" meter="1"/>
    <up_axis>Z_UP</up_axis>
  </asset>
  <library_effects>
    <effect id="PlaneMaterial-effect">
      <profile_COMMON>
        <technique sid="common">
          <lambert>
            <emission>
              <color sid="emission">0 0 0 1</color>
            </emission>
            <diffuse>
              <color sid="diffuse">0.01664001 0.8000001 0.01191879 1</color>
            </diffuse>
            <reflectivity>
              <float sid="specular">0.5</float>
            </reflectivity>
          </lambert>
        </technique>
      </profile_COMMON>
    </effect>
  </library_effects>
  <library_images/>
  <library_materials>
    <material id="PlaneMaterial-material" name="PlaneMaterial">
      <instance_effect url="#PlaneMaterial-effect"/>
    </material>
  </library_materials>
  <library_geometries>
    <geometry id="Plane-mesh" name="Plane">
      <mesh>
        <source id="Plane-mesh-positions">
          <float_array id="Plane-mesh-positions-array" count="12">-1 -1 0 1 -1 0 -1 1 0 1 1 0</float_array>
          <technique_common>
            <accessor source="#Plane-mesh-positions-array" count="4" stride="3">
              <param name="X" type="float"/>
              <param name="Y" type="float"/>
              <param name="Z" type="float"/>
            </accessor>
          </technique_common>
        </source>
        <source id="Plane-mesh-normals">
          <float_array id="Plane-mesh-normals-array" count="3">0 0 1</float_array>
          <technique_common>
            <accessor source="#Plane-mesh-normals-array" count="1" stride="3">
              <param name="X" type="float"/>
              <param name="Y" type="float"/>
              <param name="Z" type="float"/>
            </accessor>
          </technique_common>
        </source>
        <source id="Plane-mesh-map-0">
          <float_array id="Plane-mesh-map-0-array" count="12">1 0 0 1 0 0 1 0 1 1 0 1</float_array>
          <technique_common>
            <accessor source="#Plane-mesh-map-0-array" count="6" stride="2">
              <param name="S" type="float"/>
              <param name="T" type="float"/>
            </accessor>
          </technique_common>
        </source>
        <vertices id="Plane-mesh-vertices">
          <input semantic="POSITION" source="#Plane-mesh-positions"/>
        </vertices>
        <triangles material="PlaneMaterial-material" count="2">
          <input semantic="VERTEX" source="#Plane-mesh-vertices" offset="0"/>
          <input semantic="NORMAL" source="#Plane-mesh-normals" offset="1"/>
          <input semantic="TEXCOORD" source="#Plane-mesh-map-0" offset="2" set="0"/>
          <p>1 0 0 2 0 1 0 0 2 1 0 3 3 0 4 2 0 5</p>
        </triangles>
      </mesh>
    </geometry>
  </library_geometries>
  <library_visual_scenes>
    <visual_scene id="Scene" name="Scene">
      <node id="Plane" name="Plane" type="NODE">
        <matrix sid="transform">1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1</matrix>
        <instance_geometry url="#Plane-mesh" name="Plane">
          <bind_material>
            <technique_common>
              <instance_material symbol="PlaneMaterial-material" target="#PlaneMaterial-material">
                <bind_vertex_input semantic="UVMap" input_semantic="TEXCOORD" input_set="0"/>
              </instance_material>
            </technique_common>
          </bind_material>
        </instance_geometry>
      </node>
    </visual_scene>
  </library_visual_scenes>
  <scene>
    <instance_visual_scene url="#Scene"/>
  </scene>
</COLLADA>

Qt C++:

    QString planePath = ":/Models/Plane.dae";
    QFile f(planePath);
    if (!f.open(QIODevice::ReadOnly))
    {
        std::cerr << "Failed to load the file: " <<
                     planePath.toStdString() << std::endl;
        return;
    }
 
    QXmlQuery query;
    query.bindVariable("myFile", &f);
//    query.setQuery("doc($myFile)//*[local-name() = 'p']/text()"); // it works too but it is XPath 1.0
    query.setQuery("doc($myFile)//*:p/text()");
 
    QString result;
    query.evaluateTo(&result);
    qDebug() << result;
    f.close();

I try to rewrite in Python but my example is freezed on next line:

        query.setQuery("doc($myFile)//*:p/text()")
        # query.setQuery("doc($myFile)//*[local-name() = 'p']/text()")

main.py


import sys
from PySide2.QtCore import QFile, QIODevice
from PySide2.QtXmlPatterns import QXmlQuery, QXmlResultItems
from PySide2.QtWidgets import (QApplication,
    QTextEdit, QWidget)

class Window(QWidget):

    def __init__(self):
        super().__init__()
        self.resize(250, 250)
        output = QTextEdit("", self)
        planePath = "./Plane.dae"
        f = QFile(planePath)
        if not f.open(QIODevice.ReadOnly):
            print("Falied to load the file:", planePath)
        query = QXmlQuery()
        query.bindVariable("myFile", f)
        query.setQuery("doc($myFile)//*:p/text()")
        # query.setQuery("doc($myFile)//*[local-name() = 'p']/text()")
        result = QXmlResultItems()
        query.evaluateTo(result)
        output.setText("Hello")

def main():
    app = QApplication(sys.argv)
    w = Window()
    w.show()
    sys.exit(app.exec_())

if __name__ == "__main__":
    main()

Solution

  • It seems that it is a PySide2 bug and if we use gdb to debug the code then the following is obtained:

    (qt_venv) [qt_user@machine ~]$ gdb --args python main.py
    GNU gdb (GDB) 9.2
    Copyright (C) 2020 Free Software Foundation, Inc.
    License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
    This is free software: you are free to change and redistribute it.
    There is NO WARRANTY, to the extent permitted by law.
    Type "show copying" and "show warranty" for details.
    This GDB was configured as "x86_64-pc-linux-gnu".
    Type "show configuration" for configuration details.
    For bug reporting instructions, please see:
    <http://www.gnu.org/software/gdb/bugs/>.
    Find the GDB manual and other documentation resources online at:
        <http://www.gnu.org/software/gdb/documentation/>.
    
    For help, type "help".
    Type "apropos word" to search for commands related to "word"...
    Reading symbols from python...
    (No debugging symbols found in python)
    (gdb) run
    Starting program: /home/qt_user/Documents/qt_venv/bin/python main.py
    [Thread debugging using libthread_db enabled]
    Using host libthread_db library "/usr/lib/libthread_db.so.1".
    [New Thread 0x7ffff3e8f700 (LWP 27932)]
    [New Thread 0x7ffff168e700 (LWP 27933)]
    [New Thread 0x7fffeee8d700 (LWP 27934)]
    [New Thread 0x7fffe2106700 (LWP 27935)]
    [New Thread 0x7fffe149f700 (LWP 27936)]
    [New Thread 0x7fffe0a67700 (LWP 27937)]
    
    Thread 1 "python" received signal SIGSEGV, Segmentation fault.
    0x00007fffe5ba1b0c in QPatternist::TypeChecker::verifyType(QExplicitlySharedDataPointer<QPatternist::Expression> const&, QExplicitlySharedDataPointer<QPatternist::SequenceType const> const&, QExplicitlySharedDataPointer<QPatternist::StaticContext> const&, QPatternist::ReportContext::ErrorCode, QFlags<QPatternist::TypeChecker::Option>) () from /home/qt_user/Documents/qt_venv/lib/python3.8/site-packages/PySide2/Qt/lib/libQt5XmlPatterns.so.5
    (gdb) backtrace
    #0  0x00007fffe5ba1b0c in QPatternist::TypeChecker::verifyType(QExplicitlySharedDataPointer<QPatternist::Expression> const&, QExplicitlySharedDataPointer<QPatternist::SequenceType const> const&, QExplicitlySharedDataPointer<QPatternist::StaticContext> const&, QPatternist::ReportContext::ErrorCode, QFlags<QPatternist::TypeChecker::Option>) () from /home/qt_user/Documents/qt_venv/lib/python3.8/site-packages/PySide2/Qt/lib/libQt5XmlPatterns.so.5
    #1  0x00007fffe5ba38da in QPatternist::TypeChecker::applyFunctionConversion(QExplicitlySharedDataPointer<QPatternist::Expression> const&, QExplicitlySharedDataPointer<QPatternist::SequenceType const> const&, QExplicitlySharedDataPointer<QPatternist::StaticContext> const&, QPatternist::ReportContext::ErrorCode, QFlags<QPatternist::TypeChecker::Option>) () from /home/qt_user/Documents/qt_venv/lib/python3.8/site-packages/PySide2/Qt/lib/libQt5XmlPatterns.so.5
    #2  0x00007fffe5a71f41 in QPatternist::resolveVariable(QXmlName const&, YYLTYPE const&, QPatternist::ParserContext*, bool) ()
       from /home/qt_user/Documents/qt_venv/lib/python3.8/site-packages/PySide2/Qt/lib/libQt5XmlPatterns.so.5
    #3  0x00007fffe5a78616 in QPatternist::XPathparse(QPatternist::ParserContext*) ()
       from /home/qt_user/Documents/qt_venv/lib/python3.8/site-packages/PySide2/Qt/lib/libQt5XmlPatterns.so.5
    #4  0x00007fffe59cb98f in QPatternist::ExpressionFactory::createExpression(QExplicitlySharedDataPointer<QPatternist::Tokenizer> const&, QExplicitlySharedDataPointer<QPatternist::StaticContext> const&, QXmlQuery::QueryLanguage, QExplicitlySharedDataPointer<QPatternist::SequenceType const> const&, QUrl const&, QXmlName const&) ()
       from /home/qt_user/Documents/qt_venv/lib/python3.8/site-packages/PySide2/Qt/lib/libQt5XmlPatterns.so.5
    #5  0x00007fffe59cd983 in QPatternist::ExpressionFactory::createExpression(QIODevice*, QExplicitlySharedDataPointer<QPatternist::StaticContext> const&, QXmlQuery::QueryLanguage, QExplicitlySharedDataPointer<QPatternist::SequenceType const> const&, QUrl const&, QXmlName const&) ()
       from /home/qt_user/Documents/qt_venv/lib/python3.8/site-packages/PySide2/Qt/lib/libQt5XmlPatterns.so.5
    #6  0x00007fffe597be6c in QXmlQuery::setQuery(QIODevice*, QUrl const&) ()
       from /home/qt_user/Documents/qt_venv/lib/python3.8/site-packages/PySide2/Qt/lib/libQt5XmlPatterns.so.5
    #7  0x00007fffe597cba8 in QXmlQuery::setQuery(QString const&, QUrl const&) ()
       from /home/qt_user/Documents/qt_venv/lib/python3.8/site-packages/PySide2/Qt/lib/libQt5XmlPatterns.so.5
    #8  0x00007fffe5eb1ce8 in Sbk_QXmlQueryFunc_setQuery () from /home/qt_user/Documents/qt_venv/lib/python3.8/site-packages/PySide2/QtXmlPatterns.abi3.so
    #9  0x00007ffff7b3dee5 in ?? () from /usr/lib/libpython3.8.so.1.0
    #10 0x00007ffff7b2bb41 in _PyEval_EvalFrameDefault () from /usr/lib/libpython3.8.so.1.0
    #11 0x00007ffff7b2a5ba in _PyEval_EvalCodeWithName () from /usr/lib/libpython3.8.so.1.0
    #12 0x00007ffff7b3428e in _PyObject_FastCallDict () from /usr/lib/libpython3.8.so.1.0
    #13 0x00007ffff7b46d64 in ?? () from /usr/lib/libpython3.8.so.1.0
    #14 0x00007ffff7b34c90 in _PyObject_MakeTpCall () from /usr/lib/libpython3.8.so.1.0
    #15 0x00007ffff7b2ffc4 in _PyEval_EvalFrameDefault () from /usr/lib/libpython3.8.so.1.0
    #16 0x00007ffff7b3c138 in _PyFunction_Vectorcall () from /usr/lib/libpython3.8.so.1.0
    #17 0x00007ffff7b2b77c in _PyEval_EvalFrameDefault () from /usr/lib/libpython3.8.so.1.0
    #18 0x00007ffff7b2a0c4 in _PyEval_EvalCodeWithName () from /usr/lib/libpython3.8.so.1.0
    #19 0x00007ffff7bdb323 in PyEval_EvalCode () from /usr/lib/libpython3.8.so.1.0
    #20 0x00007ffff7be6b98 in ?? () from /usr/lib/libpython3.8.so.1.0
    #21 0x00007ffff7be0db3 in ?? () from /usr/lib/libpython3.8.so.1.0
    #22 0x00007ffff7a9fa0b in PyRun_FileExFlags () from /usr/lib/libpython3.8.so.1.0
    #23 0x00007ffff7a9f482 in PyRun_SimpleFileExFlags () from /usr/lib/libpython3.8.so.1.0
    #24 0x00007ffff7bf3a1a in Py_RunMain () from /usr/lib/libpython3.8.so.1.0
    #25 0x00007ffff7bcfbd9 in Py_BytesMain () from /usr/lib/libpython3.8.so.1.0
    #26 0x00007ffff7dc3002 in __libc_start_main () from /usr/lib/libc.so.6
    #27 0x000055555555504e in _start ()
    

    It is observed that an attempt is made to access an object but its memory has not been reserved. My recommendation is that you report it and probably in a next release it will be solved.

    So as your question points to using your code in python so one option is to use PyQt5:

    import sys
    from PyQt5.QtCore import QBuffer, QByteArray, QFile, QIODevice, QTextCodec
    from PyQt5.QtXmlPatterns import QXmlQuery
    from PyQt5.QtWidgets import QApplication, QTextEdit, QVBoxLayout, QWidget
    
    
    class Window(QWidget):
        def __init__(self):
            super().__init__()
            self.resize(250, 250)
            output = QTextEdit()
            lay = QVBoxLayout(self)
            lay.addWidget(output)
    
            planePath = "./Plane.dae"
            f = QFile(planePath)
            if not f.open(QIODevice.ReadOnly):
                print("Falied to load the file:", planePath)
            query = QXmlQuery()
            query.bindVariable("myFile", f)
            query.setQuery("doc($myFile)//*:p/text()")
    
            ba = QByteArray()
            buf = QBuffer(ba)
            buf.open(QIODevice.ReadWrite)
    
            query.evaluateTo(buf)
    
            text = QTextCodec.codecForName("UTF-8").toUnicode(ba)
            output.setText(text)
    
    
    def main():
        app = QApplication(sys.argv)
        w = Window()
        w.show()
        sys.exit(app.exec_())
    
    
    if __name__ == "__main__":
        main()