コンテンツが異なる場合、2つのXMLファイルで異なるタグを探す

コンテンツが異なる場合、2つのXMLファイルで異なるタグを探す

私は時々Androidアプリを翻訳します。開発者がXMLファイルを私に送ってきます。例えば

<string name="foo">Foo</string>
<string name="baz">Baz</string>

そして、各要素の内容を翻訳したXMLファイルを彼に送り返します。例えば、

<string name="foo">Translated foo</string>
<string name="baz">Translated baz</string>

問題は、開発者が新しいテキスト要素を追加し、翻訳する新しいファイルを私に送信したときに発生します。

<string name="foo">Foo</string>
<string name="bar">Bar</string>
<string name="baz">Baz</string>

質問:これを以前の翻訳ファイルと比較して、新しい属性を持つタグだけを探すにはどうしたらいいでしょうか。あるいは、もっと簡単な方法はありますか?マージ2 つのファイルで、翻訳する新しい行の先頭にマーカーを追加することは可能でしょうか?

前述の例の場合、次のようなファイルを生成することになります。

<string name="foo">Translated foo</string>
<!-- new --><string name="bar">Bar</string>
<string name="baz">Translated baz</string>

答え1

既存の解決策が見つからなかったため、自分で小さな Python スクリプトを書くことにしました。これまでのところ、これで問題は解決しているようです。

"""Usage: merge_strings.py [-m <mrk>] [-o <file> [-i]] <old_xml> <new_xml>

Substitutes the content of any 'string' tag from <new_xml> with the
content of a 'string' tag from <old_xml> with the same 'name' attribute,
if present, otherwise prepends it with <mrk>.
By default the result is printed to stdout.

Note: This program assumes that no two 'string' tags in the same file
have the same 'name' attribute. Furthermore, 'string' tags with names
unique to <old_xml> are ignored.

Options:
    -h --help                 Show this screen.
    -m <mrk> --marker <mrk>   Marker for new strings [default: ***new***].
    -o <file>                 Print to <file> instead.
    -i --interactive          Check before overwriting <file>.
"""

from os.path import isfile
from sys import exit

from docopt import docopt
import lxml.etree as etree


def merge_strings(old, new, marker):
    """
    Merge in place synonymous strings from 'old' into 'new'.
    Ignores strings unique to 'old' and prepends strings unique to
    'new' with 'marker'.
    """
    for s in new.iterfind('//string'):
        name = s.attrib['name']
        t = old.find("//string[@name='" + name + "']")

        if t is not None:
            s.text = t.text
        else:
            s.text = marker + s.text

def check_overwrite(path):
    """
    Check if we want to overwrite 'path' and exit if not.
    Defaults to no.
    """
    print("About to overwrite:", path)
    choice = input("Continue? [y/N]")

    if choice.lower() != 'y':
        exit(0)

def print_to_file(tree, path, interactive=False):
    if interactive and isfile(path):
        check_overwrite(path)

    with open(path, mode='wb') as f:
        tree.write(f, pretty_print=True,
                      encoding='utf-8',
                      xml_declaration=True)

def print_to_stdout(tree):
    print(etree.tostring(tree, pretty_print=True,
                               encoding='utf-8',
                               xml_declaration=True).decode('utf-8'))


if __name__ == '__main__':
    args = docopt(__doc__)

    old_tree = etree.parse(args['<old_xml>'])
    new_tree = etree.parse(args['<new_xml>'])

    merge_strings(old_tree, new_tree, args['--marker'])

    if args['-o']:
        print_to_file(new_tree, args['-o'], args['--interactive'])
    else:
        print_to_stdout(new_tree)

必須の出力例は次のとおりです。

$cat tests/old.xml 
<?xml version="1.0" encoding="utf-8"?>
<resources>
  <string name="foo">Translated foo</string>
  <string name="baz">Translated baz</string>
</resources>

$cat tests/new.xml 
<?xml version="1.0" encoding="utf-8"?>
<resources>
  <string name="foo">Foo</string>
  <string name="bar">Bar</string>
  <string name="baz">Baz</string>
</resources>

$python merge_strings.py old.xml new.xml                                                   
<?xml version='1.0' encoding='utf-8'?>
<resources>
  <string name="foo">Translated foo</string>
  <string name="bar">***new***Bar</string>
  <string name="baz">Translated baz</string>
</resources>

述べる:私は Python については比較的初心者で、XML についてはまったくの初心者なので、上記のコードを改善する方法についてのコメントをいただければ幸いです。

関連情報