使用 Python 向 Plist 中的深层节点添加数据

Adding data to deep node in Plist with Python

我有一个 plist,我最初从中提取信息然后需要向其中写入信息。使用 xml.etree.ElementTree,我可以访问 "WUT",但如果我尝试用数字“1”覆盖它或在它之后添加另一行“2”,则使用以下方法:

ET.SubElement(plist[0][15][1][1][1][1][1][1][1], '1')

或与

plist[0][15][1][1][1][1][1][1][1].append('1')

我会得到一个索引越界错误,因为显然节点 1 还不存在。

我需要添加到特定节点(并且在大多数情况下创建该节点)。在 WUT 下面,我想添加大约 200 个带有随机信息的字符串子项(现在假设为 1-200)。我将如何使用 plistlib 或 xml.etree.ElementTree.

执行此操作
<key>Title</key>
<dict>
  <key>Set</key>
  <dict>
    <key>Notes</key>
    <dict>
      <key>Tester</key>
      <array>
        <dict>
          <key>13</key>
          <dict>
            <key>Param</key>
            <array>
              <string>WUT</string>
            </array>
          </dict>
        </dict>
        <dict>
          <key>82</key>
          <dict>
            <key>Param</key>
            <array>
              <string>WUT</string>
            </array>
          </dict>
        </dict>
        <dict>
          <key>64</key>
          <dict>
            <key>Param</key>
            <array>
              <string>WUT</string>
            </array>
          </dict>
        </dict>

我想达到的结果是:

<key>Title</key>
    <dict>
      <key>Set</key>
      <dict>
        <key>Notes</key>
        <dict>
          <key>Tester</key>
          <array>
            <dict>
              <key>13</key>
              <dict>
                <key>Param</key>
                <array>
                  <string>1</string>
                  <string>2</string>
                  <string>3</string>
                  <string>4</string>
                  <string>5</string>
                  <string>6</string>
                  <string>7</string>
                </array>
              </dict>
            </dict>
            <dict>
              <key>82</key>
              <dict>
                <key>Param</key>
                <array>
                  <string>WUT</string>
                </array>
              </dict>
            </dict>
            <dict>
              <key>64</key>
              <dict>
                <key>Param</key>
                <array>
                  <string>WUT</string>
                </array>
              </dict>
            </dict>

*编辑这些是我试图进入正确位置的 for 循环。它似乎只适用于最后一个,但这意味着它将把我的新数据放入 "Writing" 而不是 "Notes"。

for plist_title in tree.xpath('//dict[key="Notes"][1]')
    for plist_tester in plist_title.xpath('//dict[key="13"][1]')
        plist_tester.insert(1,myData)

如果我想让它进入写作,我会做类似的事情:

for plist_title in tree.xpath('//dict[key="Writing"][1]')
    for plist_tester in plist_title.xpath('//dict[key="13"][1]')
        plist_tester.insert(1,myData)

或不同的测试员级别:

for plist_title in tree.xpath('//dict[key="Notes"][1]')
    for plist_tester in plist_title.xpath('//dict[key="82"][1]')
        plist_tester.insert(1,myData)

遗憾的是它们不起作用:(

使用 lxml 和一些 xpath 魔法:

from lxml import etree

from lxml.builder import E

plist = """
<root>
  <key>Title</key>
  <dict>
    <key>Set</key>
    <dict>
      <key>Notes</key>
      <dict>
        <key>Tester</key>
        <array>
          <dict>
            <key>13</key>
            <dict>
              <key>Param</key>
              <array>
                <string>WUT</string>
              </array>
            </dict>
          </dict>
        </array>
      </dict>
    </dict>
  </dict>
</root>"""

这将创建一个 lxml 对象(您也可以从文件加载它):

xml_data = etree.fromstring(plist)

创建候选元素数组:

array = E('array')

for num in range(10):
    array.append(E.string(str(num)))

这是它的样子:

print(etree.tostring(array, pretty_print=True))
<array>
  <string>0</string>
  <string>1</string>
  <string>2</string>
  <string>3</string>
  <string>4</string>
  <string>5</string>
  <string>6</string>
  <string>7</string>
  <string>8</string>
  <string>9</string>
</array>

从主 plist 中,获取有趣的条目,在本例中是第一个包含 WUT 的数组,将其删除并附加您想要的标签:

for first_array_contains_wut in xml_data.xpath('//array[string="WUT"][1]'):
    parent_tag = first_array_contains_wut.getparent()
    parent_tag.remove(first_array_contains_wut)
    parent_tag.append(array)

下面是修改后的最终版本:

print(etree.tostring(xml_data, pretty_print=True))
<root>
  <key>Title</key>
  <dict>
    <key>Set</key>
    <dict>
      <key>Notes</key>
      <dict>
        <key>Tester</key>
        <array>
          <dict>
            <key>13</key>
            <dict>
              <key>Param</key>
              <array><string>0</string><string>1</string><string>2</string><string>3</string><string>4</string><string>5</string><string>6</string><string>7</string><string>8</string><string>9</string></array></dict>
          </dict>
        </array>
      </dict>
    </dict>
  </dict>
</root>

来自评论:

If the key "Notes" could change around to "Writing" in the same plist, is there a way to use xpath to search for both "Writing" and the "13" key? If both Notes and writing had 13, there is no way i could be sure i have found it. I tried with 2x for loops, but i always end up with the second "13".

您可以尝试更明确的匹配,如果这是我理解您的节点的样子:

<root>
  <key>Title</key>
  <dict>
    <key>Set</key>
    <dict>
      <key>Notes</key>
      <dict>
        <key>Tester</key>
        <array>
          <dict>
            <key>13</key>
            <dict>
              <key>Param</key>
              <array>
                <string>WUT</string>
              </array>
            </dict>
          </dict>
        </array>
      </dict>
      <dict>
        <key>Writing</key>
        <dict>
          <key>Tester</key>
          <array>
            <dict>
              <key>13</key>
              <dict>
                <key>Param</key>
                <array>
                  <string>WUT2</string>
                </array>
              </dict>
            </dict>
          </array>
        </dict>
      </dict>
    </dict>
  </dict>
</root>

如果我想得到 'WUT2' 是在有 'Writing' 的字典下,你可以这样做:

In [26]: [x.text for x in xml_data.xpath('//dict[key/text()="Writing"]//string')]
Out[26]: ['WUT2']

但是,如果您在第二个“13”键之后,那么您可以执行以下操作:

In [35]: [x.text for x in xml_data.xpath('//dict[//key[text()="13"]][2]//string')]
Out[35]: ['WUT2']