XPath - 根据后续兄弟节点选择节点

3

我有一个这样的查询:

/plist/dict[1]/dict/dict/dict/key/following-sibling::dict/string[string()='Algier']

我希望选择的是 'key' 节点(就在 following-sibling::dict 之前的节点)。

XML 如下:

<?xml version="1.0" encoding="UTF-8"?>
<plist version="1.0">
    <dict>
    <key>en_GB</key>
            <dict>
                <key>Africa</key>
                <dict>
                    <key>Algeria</key>
                    <dict>
                        <key>60390</key>
                        <dict>
                            <key>NAME</key>
                            <string>Algier</string>
                            <key>LATITUDE</key>
                            <string>36.7500</string>
                            <key>LONGITUDE</key>
                            <string>3.0500</string>
                        </dict>
                        <key>60391</key>
                        <dict>
                            <key>NAME</key>
                            <string>Some other city</string>
                            <key>LATITUDE</key>
                            <string>36.7500</string>
                            <key>LONGITUDE</key>
                            <string>3.0500</string>
                        </dict>
                    </dict>
                </dict>
        </dict>
    </dict>
</plist>

换句话说,当城市名称为“Algier”时,我想选择“60390”,或者当城市名称为“其他城市”时,选择“60391”。
我是在QML XmlListModel中实现这个功能的。
更新后的代码: 使用的QML:
import QtQuick 1.0

Rectangle {
    id: container;
    width: 360
    height: 360

    function onLocationModelLoaded(){
        console.debug(weLocationModel.count);
    }

    XmlListModel{
        id: weLocationModel;
        source: "we_locations.plist";
        query: "/*/dict/dict/dict/dict/key[following-sibling::dict[1]/key[.='NAME' and following-sibling::string[1] = 'Algier']]"

        XmlRole{
            name: "cityId";
            query: "name()";
        }
        onStatusChanged: {
            if (status == XmlListModel.Ready){
                console.debug("Location Model Ready");
                container.onLocationModelLoaded();
            }
        }
    }
}

似乎嵌套的following-sibling没有起作用。 例如:

query: "/*/dict/dict/dict/dict/key[following-sibling::dict[1]/key[.='NAME']]"

这两者总是返回:

Error XPST0003 in file:///Users/Ali/qtprojects/welocationtest-build-simulator/welocationtest.app/Contents/MacOS/welocationtest, at line 2, column 97: syntax error, unexpected ], expecting )
Error XPST0003 in file:///Users/Ali/qtprojects/welocationtest-build-simulator/welocationtest.app/Contents/MacOS/welocationtest, at line 2, column 91: syntax error, unexpected ], expecting end of file
file:///Users/Ali/qtprojects/welocationtest-build-simulator/welocationtest.app/Contents/Resources/qml/welocationtest/main.qml:17:9: QML XmlRole: invalid query: "name()"
Location Model Ready
0

可能QML没有遵循XPath标准吗?这个解决方案在所有其他路径编辑器中都有效。


似乎QML存在漏洞且不符合标准... - Dimitre Novatchev
难怪诺基亚现在的处境如此糟糕。:( - zakishaheen
我仔细查看了这个问题,并且同意,如果QML在给定的XPath表达式上抛出上述错误,那么QML就存在一个bug。为什么它会期望一个 ),当没有 ( 呢?你能否提交一个bug报告? - LarsH
似乎错误是针对:query: "name()";。你实际上想要指定什么? - Dimitre Novatchev
2个回答

5

选取准确的key元素所需的XPath表达式如下:

   /*/dict/dict/dict/dict
     /key
        [following-sibling::dict[1]/key
                  [.='NAME'
                 and
                   following-sibling::string[1] = $pCity
                  ]
        ]

当$pCity被设置/替换为"Algier"时,此XPath表达式将选择:
<key>60390</key>

当$pCity被设置/替换为"其他城市"时,此XPath表达式将选择以下内容:
<key>60391</key>

XSLT基于验证:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>

 <xsl:param name="pCity" select="'Some other city'"/>

 <xsl:template match="/">
  <xsl:copy-of select=
  "/*/dict/dict/dict/dict
     /key
        [following-sibling::dict[1]/key
                  [.='NAME'
                 and
                   following-sibling::string[1] = $pCity
                  ]
        ]
  "/>
 </xsl:template>
</xsl:stylesheet>

当对所提供的XML文档应用该转换时:
<plist version="1.0">
    <dict>
        <key>en_GB</key>
        <dict>
            <key>Africa</key>
            <dict>
                <key>Algeria</key>
                <dict>
                    <key>60390</key>
                    <dict>
                        <key>NAME</key>
                        <string>Algier</string>
                        <key>LATITUDE</key>
                        <string>36.7500</string>
                        <key>LONGITUDE</key>
                        <string>3.0500</string>
                    </dict>
                    <key>60391</key>
                    <dict>
                        <key>NAME</key>
                        <string>Some other city</string>
                        <key>LATITUDE</key>
                        <string>36.7500</string>
                        <key>LONGITUDE</key>
                        <string>3.0500</string>
                    </dict>
                </dict>
            </dict>
        </dict>
    </dict>
</plist>

所期望的,正确的结果已被产生:

<key>60391</key>

当我们在上述变换中替换

 <xsl:param name="pCity" select="'Some other city'"/>

with:

 <xsl:param name="pCity" select="'Algier'"/>

重新应用转换,再次进行操作,我们就可以得到正确的结果。
<key>60390</key>

嗯,听起来很合理。不过我得在 QML 中检查一下才能将其标记为正确答案。与此同时,我会点赞的,因为它似乎在其他工具中也有效。:) 非常感谢。 - zakishaheen
1
@debuggerman:不客气。如果QML实现了XPath(应该会的),那么这个解决方案应该能够产生所需的结果。 - Dimitre Novatchev

1

如果QML XPath支持不够好,您也可以使用正则表达式来获取城市的数字:

import QtQuick 1.0

Rectangle {
    id: container

    width: 360
    height: 360

    Component.onCompleted: {
        loadWeLocationsFile();
    }

    property string weLocationsXML
    signal weLocationsLoaded()

    function loadWeLocationsFile() {
        // load file
        var doc = new XMLHttpRequest();
        doc.onreadystatechange = function() {
            if (doc.readyState == XMLHttpRequest.DONE) {
                // get file content
                weLocationsXML = doc.responseText;

                // emit signal
                weLocationsLoaded();
            }
        }

        doc.open("GET", "we_locations.plist");
        doc.send();
    }

    function getIDByName(name) {
        // escape special characters for regex (maybe there is a better way to do this)
        var safeName = name.replace(/[-.,?+*#^$()[\]{}\\|]/g, "\\$&");

        // create regex
        var regex = new RegExp("<key>(.*?)</key>\\s*<dict>\\s*<key>NAME</key>\\s*<string>" + safeName + "</string>", "m");

        // execute regex
        var match = regex.exec(weLocationsXML);

        if (match != null && match.length > 1) {
            return match[1];  // name found, group 1 contains id
        } else {
            return null;  // no such name in XML
        }
    }

    // Test it ...
    onWeLocationsLoaded: {
        console.log("Number for 'Algier':", getIDByName("Algier"));
        console.log("Number for 'NotInXML':", getIDByName("NotInXML"));
        console.log("Number for 'Some other city':", getIDByName("Some other city"));
    }
}

输出:

Number for 'Algier': 60390
Number for 'NotInXML': null
Number for 'Some other city': 60391

如果你需要在 QML 模型中使用这个数字,你可以创建一个 ListModel 并将数字添加到其中。

太棒了。非常感谢 :). 暂时解决了我的问题。 - zakishaheen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接