<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?