我如何解析自定义格式的数据?

How could I parse data coming in a custom format?

有人可以帮我在 Delphi 中解析这个吗?我正在接收以下格式的数据:

Data = {
    ["Node 1 Name"] = {
        ["Item 1 Name"] = {"short name", "Description here, which could include [] brackets"},
        ["Item 2 Name"] = {"short name", "Description here, which could include [] brackets"},
    },
    ["Node 2 Name"] = {
        ["Item 1 Name"] = {"short name", "Description here, which could include [] brackets"},
        ["Item 2 Name"] = {"short name", "Description here, which could include [] brackets"},
    },
    ["Node 3 Name"] = {
        ["Item 1 Name"] = {"short name", "Description here, which could include [] brackets"},
        ["Item 2 Name"] = {"short name", "Description here, which could include [] brackets"},
    }
}

基本上,我需要能够获取所有节点(它们的名称),以及与每个节点关联的所有项目名称及其短名称和描述。项目和节点的数量可以是可变的。这是最终结构,数据不能有更多的级别或值。

我为您在问题中显示的数据设计了一个非常简单的解析器。

是在他自己的单元里我给demo起名为ParseCustomDataParser

要使用它,请在源代码(可能是 TForm)的 uses 子句中添加 ParseCustomDataParser。然后你可以像这样使用它:

procedure TForm1.Button1Click(Sender: TObject);
const
    SampleData : String =
        'Data = {' +
        '    ["Node 1 Name"] = {' +
        '        ["Item 1 Name"] = {"short name 1/1", "Description here, which could include [] brackets"},' +
        '        ["Item 2 Name"] = {"short name 1/2", "Description here, which could include [] brackets"},' +
        '    },' +
        '    ["Node 2 Name"] = {' +
        '        ["Item 1 Name"] = {"short name 2/1", "Description here, which could include [] brackets"},' +
        '        ["Item 2 Name"] = {"short name 2/2", "Description here, which could include [] brackets"},' +
        '    },' +
        '    ["Node 3 Name"] = {' +
        '        ["Item 1 Name"] = {"short name 3/1", "Description here, which could include [] brackets"},' +
        '        ["Item 2 Name"] = {"short name 3/2", "Description here, which could include [] brackets"},' +
        '    }' +
        '}';
var
    Index      : Integer;
    NRow       : Integer;
    NItem      : Integer;
    CustomData : TCustomData;
begin
    // Parse the source of data
    try
        Index := 1;
        CustomData.Parse(SampleData, Index);
    except
        on E:Exception do begin
            Memo1.Lines.Add(E.Message);
            Exit;
        end;
    end;

    // Display the parsed result
    Memo1.Lines.Add(CustomData.Name);
    for NRow := 0 to High(CustomData.Rows) do begin
        Memo1.Lines.Add('  ' + CustomData.Rows[NRow].Name);
        for NItem := 0 to High(CustomData.Rows[NRow].Items) do begin
            Memo1.Lines.Add(
                '    Name="' + CustomData.Rows[NRow].Items[NItem].ShortName + '"' +
                '    Description="' + CustomData.Rows[NRow].Items[NItem].Description + '"');
        end;
    end;
end;

解析器将字符串作为数据源,并将索引作为字符串的起始位置(可能应该为 1)。

解析是作为包含结果的记录 TCustomData 的方法实现的。该记录包含一组行,每行由一组项目组成。

我对格式做了一些假设,因为你只是展示了一个例子,但没有完整的规范。例如,为了简单起见,我假设双引号中的字符串不包含字符双引号本身。另一个例子是,您的样本数据在项目列表的末尾有一个额外的逗号,而 不应该是一个。如果存在的话,我只是忽略了额外的昏迷。要根据我的代码制作真实世界的代码,您还应该注意许多其他细节。鉴于没有规范,这是我能做的最好的。你开始了。现在由您根据我的代码构建您的解决方案。

解析器源代码:

unit ParseCustomDataParser;

interface

uses
    System.SysUtils;

const
    UnexpectedDelimiter = 'Unexpected delimiter at index %d "%s", ' +
                          'should be "%s"';

type
  TCustomDataItem = record
      Name        : String;
      ShortName   : String;
      Description : String;
      procedure Clear;
      procedure Parse(const Src : String; var Index : Integer);
  end;
  TCustomDataRow = record
      Name  : String;
      Items : array of TCustomDataItem;
      procedure Clear;
      procedure Parse(const Src : String; var Index : Integer);
  end;
  TCustomData = record
      Name : String;
      Rows : array of TCustomDataRow;
      procedure Clear;
      procedure Parse(const Src : String; var Index : Integer);
  end;


implementation

{ Support routines }

procedure SkipSpaces(const Src : String; var Index : Integer);
begin
    while (Index <= Length(Src)) and
          (CharInSet(Src[Index], [' ', #9, #10, #13])) do
        Inc(Index);
end;

function GetNextDelimiter(const Src : String; var Index : Integer) : Char;
begin
    SkipSpaces(Src, Index);
    if Index > Length(Src) then
        Result := #0
    else begin
        Result := Src[Index];
        Inc(Index);
    end;
end;

function GetNextToken(const Src : String; var Index : Integer) : String;
var
    N : Integer;
begin
    SkipSpaces(Src, Index);
    N := Index;
    while (Index <= Length(Src)) and
          (CharInSet(Src[Index], ['a'..'z', 'A'..'Z'])) do
        Inc(Index);
    Result := Copy(Src, N, Index - N);
end;

function GetNextQuotedString(const Src : String; var Index : Integer) : String;
var
    Delimiter : Char;
    N         : Integer;
begin
    Delimiter := GetNextDelimiter(Src, Index);
    if Delimiter <> '"' then
        raise Exception.CreateFmt(UnexpectedDelimiter,
                                  [Index, Delimiter, '"']);
    N := Index;
    // Find closing double quote
    while (Index <= Length(Src)) and (Src[Index] <> '"') do
        Inc(Index);
    Result := Copy(Src, N, Index - N);
    if (Index <= Length(Src)) and (Src[Index] = '"') then
        Inc(Index);   // Skip closing double quote
end;

{ TCustomData }

procedure TCustomData.Clear;
begin
    Name := '';
    SetLength(Rows, 0);
end;

procedure TCustomData.Parse(const Src: String; var Index : Integer);
var
    Token     : String;
    Delimiter : Char;
    NRow      : Integer;
begin
    Clear;
    while Index <= Length(Src) do begin
        Token := GetNextToken(Src, Index);
        if Token = '' then
           break;
        Name := Token;
        Delimiter := GetNextDelimiter(Src, Index);
        if Delimiter <> '=' then
            raise Exception.CreateFmt(UnexpectedDelimiter,
                                      [Index, Delimiter, '=']);
        Delimiter := GetNextDelimiter(Src, Index);
        if Delimiter <> '{' then
            raise Exception.CreateFmt(UnexpectedDelimiter,
                                      [Index, Delimiter, '{']);
        while TRUE do begin
            // Add a new row
            NRow := Length(Rows);
            SetLength(Rows, NRow + 1);
            Rows[NRow].Parse(Src, Index);
            Delimiter := GetNextDelimiter(Src, Index);
            if Delimiter <> '}' then
                raise Exception.CreateFmt(UnexpectedDelimiter,
                                          [Index, Delimiter, '{']);
            Delimiter := GetNextDelimiter(Src, Index);
            if Delimiter <> ',' then begin
                Dec(Index);
                break;
            end;
        end;
    end;
end;

{ TCustomDataRow }

procedure TCustomDataRow.Clear;
begin
    Name := '';
    SetLength(Items, 0);
end;

procedure TCustomDataRow.Parse(const Src: String; var Index : Integer);
var
    Delimiter    : Char;
    QuotedString : String;
    NRow         : Integer;
begin
    Delimiter := GetNextDelimiter(Src, Index);
    if Delimiter <> '[' then
        raise Exception.CreateFmt(UnexpectedDelimiter,
                                  [Index, Delimiter, '[']);
    QuotedString := GetNextQuotedString(Src, Index);
    if QuotedString = '' then
        raise Exception.CreateFmt('Missing quoted string at index %d', [Index]);
    Name := QuotedString;
    Delimiter := GetNextDelimiter(Src, Index);
    if Delimiter <> ']' then
        raise Exception.CreateFmt(UnexpectedDelimiter,
                                  [Index, Delimiter, ']']);
    Delimiter := GetNextDelimiter(Src, Index);
    if Delimiter <> '=' then
        raise Exception.CreateFmt(UnexpectedDelimiter,
                                  [Index, Delimiter, '=']);
    Delimiter := GetNextDelimiter(Src, Index);
    if Delimiter <> '{' then
        raise Exception.CreateFmt(UnexpectedDelimiter,
                                  [Index, Delimiter, '{']);
    while TRUE do begin
        NRow := Length(Items);
        SetLength(Items, NRow + 1);
        Items[NRow].Parse(Src, Index);
        Delimiter := GetNextDelimiter(Src, Index);
        if Delimiter <> ',' then begin
            Dec(Index);   // Go back to the delimiter
            break;
        end;
        // Last item is allowed to have an extra coma
        Delimiter := GetNextDelimiter(Src, Index);
        Dec(Index);   // Go back to the delimiter
        if Delimiter <> '[' then
            break;
    end;
end;

{ TCustomDataItem }

procedure TCustomDataItem.Clear;
begin
    Name        := '';
    ShortName   := '';
    Description := '';
end;

procedure TCustomDataItem.Parse(const Src: String; var Index: Integer);
var
    Delimiter    : Char;
    QuotedString : String;
begin
    Delimiter := GetNextDelimiter(Src, Index);
    if Delimiter <> '[' then
        raise Exception.CreateFmt(UnexpectedDelimiter,
                                  [Index, Delimiter, '[']);
    QuotedString := GetNextQuotedString(Src, Index);
    if QuotedString = '' then
        raise Exception.CreateFmt('Missing quoted string at index %d', [Index]);
    Name := QuotedString;
    Delimiter := GetNextDelimiter(Src, Index);
    if Delimiter <> ']' then
        raise Exception.CreateFmt(UnexpectedDelimiter,
                                  [Index, Delimiter, ']']);
    Delimiter := GetNextDelimiter(Src, Index);
    if Delimiter <> '=' then
        raise Exception.CreateFmt(UnexpectedDelimiter,
                                  [Index, Delimiter, '=']);
    Delimiter := GetNextDelimiter(Src, Index);
    if Delimiter <> '{' then
        raise Exception.CreateFmt(UnexpectedDelimiter,
                                  [Index, Delimiter, '{']);
    QuotedString := GetNextQuotedString(Src, Index);
    if QuotedString = '' then
        raise Exception.CreateFmt('Missing quoted string at index %d', [Index]);
    ShortName := QuotedString;
    Delimiter := GetNextDelimiter(Src, Index);
    if Delimiter <> ',' then
        raise Exception.CreateFmt(UnexpectedDelimiter,
                                  [Index, Delimiter, ',']);
    QuotedString := GetNextQuotedString(Src, Index);
    if QuotedString = '' then
        raise Exception.CreateFmt('Missing quoted string at index %d', [Index]);
    Description := QuotedString;
    Delimiter := GetNextDelimiter(Src, Index);
    if Delimiter <> '}' then
        raise Exception.CreateFmt(UnexpectedDelimiter,
                                  [Index, Delimiter, '}']);
end;

end.