static void on_start_element(void* p_user_data,
                               const xmlChar* name,
                               const xmlChar** attributes) {
    user_data_t& user_data = *(static_cast<user_data_t*>(p_user_data));
    user_data.is_at_repository_name = false;
    user_data.is_at_filename = false;

    if (xmlStrEqual(name, reinterpret_cast<const xmlChar*>("fileobject"))) {
      // a filename tag is required after a fileobject tag
      user_data.has_filename = false;
    } else if (xmlStrEqual(name, reinterpret_cast<const xmlChar*>("repository_name"))) {
      // repository_name establishes the repository name for future hashdigest values
      user_data.is_at_repository_name = true;
      user_data.has_repository_name = false;
    } else if (xmlStrEqual(name, reinterpret_cast<const xmlChar*>("filename"))) {
      // a filename tag establishes the filename for future hashdigest values
      user_data.is_at_filename = true;
      user_data.has_filename = false;
    } else if (xmlStrEqual(name, reinterpret_cast<const xmlChar*>("byte_run"))) {
      parse_byte_run_attributes(user_data, attributes);
    } else if (xmlStrEqual(name, reinterpret_cast<const xmlChar*>("hashdigest"))) {
      user_data.is_at_hashdigest = true;
      parse_hashdigest_attributes(user_data, attributes);
    }
  }
  static void on_start_element(void* p_user_data,
                               const xmlChar* name,
                               const xmlChar** attributes) {

    // set up convenient reference to user data
    user_data_t& user_data = *(static_cast<user_data_t*>(p_user_data));

    // set state based on tag name

    // fileobject
    if (xmlStrEqual(name, reinterpret_cast<const xmlChar*>("fileobject"))) {
      user_data.under_fileobject = true;

      // clear fields under fileobject
      user_data.fileobject_repository_name = user_data.default_repository_name;
      user_data.fileobject_filename = "";
      user_data.fileobject_filesize = "";
      user_data.fileobject_hashdigest_type = "";
      user_data.fileobject_hashdigest = "";

    // repository_name
    } else if (xmlStrEqual(name, reinterpret_cast<const xmlChar*>("repository_name"))) {
      user_data.under_repository_name = true;

    // filename
    } else if (xmlStrEqual(name, reinterpret_cast<const xmlChar*>("filename"))) {
      user_data.under_filename = true;

    // file size
    } else if (xmlStrEqual(name, reinterpret_cast<const xmlChar*>("filesize"))) {
      user_data.under_filesize = true;

    // byte_run
    } else if (xmlStrEqual(name, reinterpret_cast<const xmlChar*>("byte_run"))) {
      user_data.under_byte_run = true;

      // clear fields under byte_run
      user_data.byte_run_file_offset = "";
      user_data.byte_run_len = "";
      user_data.byte_run_hash_label = "";
      user_data.byte_run_hashdigest_type = "";
      user_data.byte_run_hashdigest = "";

      parse_byte_run_attributes(user_data, attributes);

    // hashdigest
    } else if (xmlStrEqual(name, reinterpret_cast<const xmlChar*>("hashdigest"))) {
      if (user_data.under_byte_run) {
        user_data.under_byte_run_hashdigest = true;
        parse_byte_run_hashdigest_attributes(user_data, attributes);
      } else {
        user_data.under_fileobject_hashdigest = true;
        parse_fileobject_hashdigest_attributes(user_data, attributes);
      }

    } else {
      // no action for other tag names
    }
  }