Skip to content

How to Parse and Edit PGN Tags

This guide shows how to parse a PGN string into a game handle, inspect existing header tags, modify or remove tags, and export the updated game back to PGN using the libscid C ABI.


1. Overview of Symbols

  • scid_position_create_from_fen: Initialises the starting board position against which moves are validated.
  • scid_game_create: Parses a PGN text buffer and constructs a game handle.
  • scid_game_tag_count_get: Retrieves the number of header tags present on the game.
  • scid_game_tag_at_get: Reads the key name and string value of a tag at a specified 0-based index using caller-allocated buffers.
  • scid_game_tag_set: Sets or overwrites a tag value.
  • scid_game_tag_remove: Deletes a tag from the game header.
  • scid_game_to_pgn: Serialises the game tree and headers back to a PGN-formatted string.

2. Complete Recipe

#include <scid/scid.h>

#include <stdio.h>
#include <string.h>

static int
check(
    scid_error  error,
    const char* call)
{
    if (error == SCID_OK)
    {
        return 1;
    }

    fprintf(stderr, "%s failed with scid_error %hu\n", call, error);
    return 0;
}


static int
contains(
    const char* text,
    const char* needle)
{
    return strstr(text, needle) != NULL;
}


int
main(void)
{
    const char*    start_fen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
    const char*    pgn = "[Event \"St Petersburg final\"]\n"
                         "[Site \"St Petersburg\"]\n"
                         "[Date \"1914.05.18\"]\n"
                         "[Round \"7\"]\n"
                         "[White \"Lasker, Emanuel\"]\n"
                         "[Black \"Capablanca, Jose Raul\"]\n"
                         "[Result \"1-0\"]\n"
                         "[ECO \"C68\"]\n"
                         "[EventDate \"1914.04.21\"]\n"
                         "[Annotator \"Example\"]\n"
                         "\n"
                         "1. e4 e5 2. Nf3 Nc6 3. Bb5 a6 1-0\n";
    scid_game*     game = NULL;
    scid_position* position = NULL;
    char           diagnostic[1024];
    char           name[64];
    char           value[256];
    char           encoded[4096];
    size_t         diagnostic_size = 0;
    size_t         name_size = 0;
    size_t         value_size = 0;
    size_t         encoded_size = 0;
    size_t         tag_count = 0;
    int            removed = 0;

    if (!check(
            scid_position_create_from_fen(start_fen, &position), "scid_position_create_from_fen") ||
        !check(
            scid_game_create(
                position, pgn, strlen(pgn), &game, diagnostic, sizeof(diagnostic),
                &diagnostic_size),
            "scid_game_create"))
    {
        fprintf(stderr, "%.*s\n", (int)diagnostic_size, diagnostic);
        scid_position_free(position);
        return 1;
    }

    if (!check(scid_game_tag_count_get(game, &tag_count), "scid_game_tag_count_get"))
    {
        scid_position_free(position);
        scid_game_free(game);
        return 1;
    }

    for (size_t i = 0; i < tag_count; ++i)
    {
        if (!check(
                scid_game_tag_at_get(
                    game, i, name, sizeof(name), &name_size, value, sizeof(value), &value_size),
                "scid_game_tag_at_get"))
        {
            scid_position_free(position);
            scid_game_free(game);
            return 1;
        }

        printf("%.*s: %.*s\n", (int)name_size, name, (int)value_size, value);
    }

    if (!check(scid_game_tag_set(game, "Annotator", "C ABI example"), "scid_game_tag_set") ||
        !check(scid_game_tag_remove(game, "EventDate", &removed), "scid_game_tag_remove") ||
        !check(
            scid_game_to_pgn(game, NULL, encoded, sizeof(encoded), &encoded_size),
            "scid_game_to_pgn"))
    {
        scid_position_free(position);
        scid_game_free(game);
        return 1;
    }

    printf("\n%.*s", (int)encoded_size, encoded);

    if (!removed || !contains(encoded, "[Annotator \"C ABI example\"]") ||
        contains(encoded, "[EventDate "))
    {
        scid_position_free(position);
        scid_game_free(game);
        return 1;
    }

    scid_position_free(position);
    scid_game_free(game);
    return 0;
}

3. Step-by-Step Breakdown

Parsing the Game

Initialise a starting position from standard FEN, then pass the PGN text buffer to scid_game_create. If syntax errors occur during parsing, diagnostic text is written into the caller's diagnostic buffer:

scid_position* position = NULL;
scid_position_create_from_fen(start_fen, &position);

scid_game* game = NULL;
char diagnostic[1024];
size_t diagnostic_size = 0;

scid_error err = scid_game_create(
    position, pgn, strlen(pgn), &game, diagnostic, sizeof(diagnostic), &diagnostic_size);

Inspecting Tags

Tags can be enumerated using 0-based indices. Call scid_game_tag_count_get followed by scid_game_tag_at_get:

size_t tag_count = 0;
scid_game_tag_count_get(game, &tag_count);

for (size_t i = 0; i < tag_count; ++i)
{
    char name[64];
    char value[256];
    size_t name_size = 0;
    size_t value_size = 0;

    scid_game_tag_at_get(
        game, i, name, sizeof(name), &name_size, value, sizeof(value), &value_size);
    printf("%.*s: %.*s\n", (int)name_size, name, (int)value_size, value);
}

Modifying and Exporting

Use scid_game_tag_set to add or update tags, scid_game_tag_remove to delete tags, and scid_game_to_pgn to export the modified game:

scid_game_tag_set(game, "Annotator", "C ABI example");

int removed = 0;
scid_game_tag_remove(game, "EventDate", &removed);

char encoded[4096];
size_t encoded_size = 0;
scid_game_to_pgn(game, NULL, encoded, sizeof(encoded), &encoded_size);

Remember to release handles with scid_position_free(position) and scid_game_free(game).