web-dev-qa-db-ja.com

外部ライブラリなしでxmlファイルをネストされた要素とマージする

Pythonを使用し、外部ライブラリを使用せずに、複数のXMLファイルをマージしようとしています。XMLファイルにはネストされた要素があります。

サンプルファイル1:

<root>
  <element1>textA</element1>
  <elements>
    <nested1>text now</nested1>
  </elements>
</root>

サンプルファイル2:

<root>
  <element2>textB</element2>
  <elements>
    <nested1>text after</nested1>
    <nested2>new text</nested2>
  </elements>
</root>

欲しいもの:

<root>
  <element1>textA</element1>    
  <element2>textB</element2>  
  <elements>
    <nested1>text after</nested1>
    <nested2>new text</nested2>
  </elements>  
</root>  

私が試したこと:

から この答え

from xml.etree import ElementTree as et
def combine_xml(files):
    first = None
    for filename in files:
        data = et.parse(filename).getroot()
        if first is None:
            first = data
        else:
            first.extend(data)
    if first is not None:
        return et.tostring(first)

私が得るもの:

<root>
  <element1>textA</element1>
  <elements>
    <nested1>text now</nested1>
  </elements>
  <element2>textB</element2>
  <elements>
    <nested1>text after</nested1>
    <nested2>new text</nested2>
  </elements>
</root>

私の問題を見て理解していただければ幸いです。私は適切な解決策を探しています、どんなガイダンスも素晴らしいでしょう。

問題を明確にするために、私が持っている現在の解決策を使用して、ネストされた要素はマージされません。

14
Inbar Rose

あなたが投稿したコードは、同じタグを持つ要素がすでに存在するかどうかに関係なく、すべての要素を結合しています。したがって、XMLファイルを処理する標準的な方法ではないため、要素を反復処理し、適切と思われる方法で手動でチェックして組み合わせる必要があります。私はそれをコードよりよく説明することはできないので、ここに多かれ少なかれコメントがあります:

from xml.etree import ElementTree as et

class XMLCombiner(object):
    def __init__(self, filenames):
        assert len(filenames) > 0, 'No filenames!'
        # save all the roots, in order, to be processed later
        self.roots = [et.parse(f).getroot() for f in filenames]

    def combine(self):
        for r in self.roots[1:]:
            # combine each element with the first one, and update that
            self.combine_element(self.roots[0], r)
        # return the string representation
        return et.tostring(self.roots[0])

    def combine_element(self, one, other):
        """
        This function recursively updates either the text or the children
        of an element if another element is found in `one`, or adds it
        from `other` if not found.
        """
        # Create a mapping from tag name to element, as that's what we are fltering with
        mapping = {el.tag: el for el in one}
        for el in other:
            if len(el) == 0:
                # Not nested
                try:
                    # Update the text
                    mapping[el.tag].text = el.text
                except KeyError:
                    # An element with this name is not in the mapping
                    mapping[el.tag] = el
                    # Add it
                    one.append(el)
            else:
                try:
                    # Recursively process the element, and update it in the same way
                    self.combine_element(mapping[el.tag], el)
                except KeyError:
                    # Not in the mapping
                    mapping[el.tag] = el
                    # Just add it
                    one.append(el)

if __name__ == '__main__':
    r = XMLCombiner(('sample1.xml', 'sample2.xml')).combine()
    print '-'*20
    print r
20
jadkik94

ありがとうございますが、私の問題は属性も考慮してマージすることでした。これが私のパッチの後のコードです:

    import sys
    from xml.etree import ElementTree as et


    class hashabledict(dict):
        def __hash__(self):
            return hash(Tuple(sorted(self.items())))


    class XMLCombiner(object):
        def __init__(self, filenames):
            assert len(filenames) > 0, 'No filenames!'
            # save all the roots, in order, to be processed later
            self.roots = [et.parse(f).getroot() for f in filenames]

    def combine(self):
        for r in self.roots[1:]:
            # combine each element with the first one, and update that
            self.combine_element(self.roots[0], r)
        # return the string representation
        return et.ElementTree(self.roots[0])

    def combine_element(self, one, other):
        """
        This function recursively updates either the text or the children
        of an element if another element is found in `one`, or adds it
        from `other` if not found.
        """
        # Create a mapping from tag name to element, as that's what we are fltering with
        mapping = {(el.tag, hashabledict(el.attrib)): el for el in one}
        for el in other:
            if len(el) == 0:
                # Not nested
                try:
                    # Update the text
                    mapping[(el.tag, hashabledict(el.attrib))].text = el.text
                except KeyError:
                    # An element with this name is not in the mapping
                    mapping[(el.tag, hashabledict(el.attrib))] = el
                    # Add it
                    one.append(el)
            else:
                try:
                    # Recursively process the element, and update it in the same way
                    self.combine_element(mapping[(el.tag, hashabledict(el.attrib))], el)
                except KeyError:
                    # Not in the mapping
                    mapping[(el.tag, hashabledict(el.attrib))] = el
                    # Just add it
                    one.append(el)

if __name__ == '__main__':

    r = XMLCombiner(sys.argv[1:-1]).combine()
    print '-'*20
    print et.tostring(r.getroot())
    r.write(sys.argv[-1], encoding="iso-8859-1", xml_declaration=True)
3
Joseph Thomas