From 1aa08dcd4087ead88cfa6ebdcd144a70eefdcf6d Mon Sep 17 00:00:00 2001 From: gaoflow Date: Thu, 25 Jun 2026 02:16:28 +0200 Subject: [PATCH 1/2] pgn: accept tag pairs with leading whitespace (fixes #1115) The PGN standard specifies that whitespace is not significant. `read_game()` was checking `line.startswith("[")` and matching `TAG_REGEX` against the raw line, both of which fail when a tag pair is preceded by horizontal whitespace (e.g. a file that begins with " [Event ...]"). Strip leading whitespace before the `startswith` guard and the regex match so that indented tag pairs are recognised correctly. --- chess/pgn.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/chess/pgn.py b/chess/pgn.py index 5ae5b43b..3d4d7e5f 100644 --- a/chess/pgn.py +++ b/chess/pgn.py @@ -1636,13 +1636,14 @@ def read_game(handle: TextIO, *, Visitor: Any = GameBuilder) -> Any: if not isinstance(managed_headers, Headers): unmanaged_headers = Headers({}) - if not line.startswith("["): + stripped = line.lstrip() + if not stripped.startswith("["): break consecutive_empty_lines = 0 if not skipping_game: - tag_match = TAG_REGEX.match(line) + tag_match = TAG_REGEX.match(stripped) if tag_match: visitor.visit_header(tag_match.group(1), tag_match.group(2)) if unmanaged_headers is not None: From b133b5f62cc9df1628adde32e9c11d848fd3e84a Mon Sep 17 00:00:00 2001 From: Vincent Gao Date: Fri, 26 Jun 2026 11:51:51 +0200 Subject: [PATCH 2/2] Add PGN leading whitespace header test --- test.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/test.py b/test.py index cc8a95b7..ac906638 100755 --- a/test.py +++ b/test.py @@ -2233,6 +2233,24 @@ def test_read_game(self): self.assertEqual(sixth_game.headers["White"], "Deep Blue (Computer)") self.assertEqual(sixth_game.headers["Result"], "1-0") + def test_read_game_with_leading_whitespace_before_header(self): + pgn = io.StringIO( + ' [Event "TCEC Season 27 - Entrance League"]\n' + '[Site "https://tcec-chess.com"]\n' + '[White "Patricia 3.1_dev_ca7ef0a3"]\n' + '[Black "Weiss 2.1-dev11"]\n' + '[Result "*"]\n' + "\n" + "1. d4 *" + ) + + game = chess.pgn.read_game(pgn) + + self.assertEqual(game.headers["Event"], "TCEC Season 27 - Entrance League") + self.assertEqual(game.headers["White"], "Patricia 3.1_dev_ca7ef0a3") + self.assertEqual(game.next().move, chess.Move.from_uci("d2d4")) + self.assertEqual(game.errors, []) + def test_read_game_with_multicomment_move(self): pgn = io.StringIO("1. e4 {A common opening} 1... e5 {A common response} {An uncommon comment}") game = chess.pgn.read_game(pgn)