1
<line fen="rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1">
    <move m="e4"/>
    <move m="e5"/>
    <move m="d4"/>
    <move m="exd4"/>
    <move m="Qxd4"/>
    <move m="Nc6"/>
    <move m="Qe3"/>

    <line fen="r1bqkbnr/pppp1ppp/2n5/8/4P3/4Q3/PPP2PPP/RNB1KBNR b KQkq - 0 4">
        <move m="Bb4+"/>

        <line fen="r1bqk1nr/pppp1ppp/2n5/8/1b2P3/4Q3/PPP2PPP/RNB1KBNR w KQkq - 0 5">
            <move m="Nc3"/>

            <line fen="r1bqk1nr/pppp1ppp/2n5/8/1b2P3/2N1Q3/PPP2PPP/R1B1KBNR b KQkq - 0 5">
                <move m="Nf6"/>
            </line>

            <move m="Nge7"/>
            <move m="Bd2"/>
            <move m="O-O"/>
            <move m="O-O-O"/>
            <move m="d6"/>
            <move m="Qg3"/>
            <move m="Kh8"/>
            <move m="f4"/>
            <move m="f5"/>
        </line>

        <move m="c3"/>
        <move m="Be7"/>
    </line>

    <line fen="r1bqkbnr/pppp1ppp/2n5/8/4P3/4Q3/PPP2PPP/RNB1KBNR b KQkq - 0 4">
        <move m="g6"/>
    </line>

    <move m="Nf6"/>
</line>
import xml.etree.ElementTree as ET
import chess
import chess.pgn

xml_data = """<line fen="rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1">
    <move m="e4"/>
    <move m="e5"/>
    <move m="d4"/>
    <move m="exd4"/>
    <move m="Qxd4"/>
    <move m="Nc6"/>
    <move m="Qe3"/>

    <line fen="r1bqkbnr/pppp1ppp/2n5/8/4P3/4Q3/PPP2PPP/RNB1KBNR b KQkq - 0 4">
        <move m="Bb4+"/>

        <line fen="r1bqk1nr/pppp1ppp/2n5/8/1b2P3/4Q3/PPP2PPP/RNB1KBNR w KQkq - 0 5">
            <move m="Nc3"/>

            <line fen="r1bqk1nr/pppp1ppp/2n5/8/1b2P3/2N1Q3/PPP2PPP/R1B1KBNR b KQkq - 0 5">
                <move m="Nf6"/>
            </line>

            <move m="Nge7"/>
            <move m="Bd2"/>
            <move m="O-O"/>
            <move m="O-O-O"/>
            <move m="d6"/>
            <move m="Qg3"/>
            <move m="Kh8"/>
            <move m="f4"/>
            <move m="f5"/>
        </line>

        <move m="c3"/>
        <move m="Be7"/>
    </line>

    <line fen="r1bqkbnr/pppp1ppp/2n5/8/4P3/4Q3/PPP2PPP/RNB1KBNR b KQkq - 0 4">
        <move m="g6"/>
    </line>

    <move m="Nf6"/>
</line>
"""

def collect_main_line(xml_elem):
    """Собирает ходы только из высшего уровня <move> — без вложенных <line>."""
    moves = []
    for child in xml_elem:
        if child.tag.lower() == "move":
            m = child.get("m")
            if m:
                moves.append(m)
    return moves


def xml_to_mainline_pgn(root_line):
    # Создаём игру
    game = chess.pgn.Game()

    # Устанавливаем начальную позицию, если указан FEN
    fen = root_line.get("fen")
    if fen:
        board = chess.Board(fen)
        game.setup(board)
    else:
        board = chess.Board()
        game.setup(board)

    moves_san = collect_main_line(root_line)

    node = game

    for san in moves_san:
        move = board.parse_san(san)
        board.push(move)
        node = node.add_main_variation(move)

    return game


def main():
    root = ET.fromstring(xml_data.strip())
    game = xml_to_mainline_pgn(root)

    exporter = chess.pgn.StringExporter(headers=True, variations=False, comments=False)
    pgn_text = game.accept(exporter)

    print(pgn_text)

    with open("main_line_only.pgn", "w", encoding="utf-8") as f:
        f.write(pgn_text)

    print("PGN сохранён в main_line_only.pgn")


if __name__ == "__main__":
    main()

I have an XML-like structure that describes a chess line with nested <move> and <line> tags.

I’m trying to convert this structure into a PGN file using the python-chess library.

I already know how to add the mainline moves to a chess.pgn.Game(), but I can’t figure out how to correctly add variations and nested sub-variations to the PGN tree.

How do I recursively add moves and variations to python-chess so that each nested <line> becomes a proper PGN variation (i.e., (...))?

Should I manually walk the tree and use node.add_variation(move) for every branch? Or is there a more idiomatic way to create PGN with sub-variations using python-chess?

New contributor
Ildar is a new contributor to this site. Take care in asking for clarification, commenting, and answering. Check out our Code of Conduct.

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.