2004. július 8., csütörtök

Building an Easy-to-Use Parser/Parsing Framework (Part II)


Problem/Question/Abstract:

How to create a simple parsing framework to parse any kind of data?

Answer:

Welcome to the second part of my article "Building an Easy-to-Use Parser/Parsing Framework". This time, I want to show you how to create a real working dtd parser as exemplified in the first part. If you don't read my first article, please make up for this now:

Building an Easy-to-Use Parser/Parsing Framework (Part I)

As mentioned earlier, we need a dtd document which holds up all our parsed informations in an easy-to-access object model. Take a look at the following interface section:

type
  { TDTDAttributeTyp }

  TDTDAttributeTyp =
    (atData, atID, atIDRef, atEnumeration);

  { TDTDAttributeStatus }

  TDTDAttributeStatus =
    (asDefault, asImplied, asRequired, asFixed);

  { TDTDChildTyp }

  TDTDChildTyp =
    (ctElement, ctChoice, ctSequence);

  { TDTDElementTyp }

  TDTDElementTyp =
    (etAny, etEmpty, etData, etContainer);

  { TDTDElementStatus }

  TDTDElementStatus =
    (esRequired, esRequiredSeq, esOptional, esOptionalSeq);

  { TDTDItem }

  TDTDItem = class(TCollectionItem)
  private
    { Private declarations }
    FName: string;
  public
    { Public declarations }
    procedure Assign(Source: TPersistent); override;
  published
    { Published declarations }
    property Name: string read FName write FName;
  end;

  { TDTDItems }

  TDTDItems = class(TCollection)
  private
    { Private declarations }
    function GetItem(Index: Integer): TDTDItem;
    procedure SetItem(Index: Integer; Value: TDTDItem);
  public
    { Public declarations }
    function Add: TDTDItem;
    function Find(const Name: string): TDTDItem;
    property Items[Index: Integer]: TDTDItem read GetItem write SetItem;
    default;
  end;

  { TDTDEntity }

  TDTDEntity = class(TDTDItem)
  private
    { Private declarations }
  public
    { Public declarations }
    procedure Assign(Source: TPersistent); override;
  published
    { Published declarations }
  end;

  { TDTDEntities }

  TDTDEntities = class(TDTDItems)
  private
    { Private declarations }
    function GetItem(Index: Integer): TDTDEntity;
    procedure SetItem(Index: Integer; Value: TDTDEntity);
  public
    { Public declarations }
    function Add: TDTDEntity;
    function Find(const Name: string): TDTDEntity;
    property Items[Index: Integer]: TDTDEntity read GetItem write SetItem;
    default;
  end;

  { TDTDEnum }

  TDTDEnum = class(TDTDItem)
  private
    { Private declarations }
  public
    { Public declarations }
    procedure Assign(Source: TPersistent); override;
  published
    { Published declarations }
  end;

  { TDTDEnums }

  TDTDEnums = class(TDTDItems)
  private
    { Private declarations }
    function GetItem(Index: Integer): TDTDEnum;
    procedure SetItem(Index: Integer; Value: TDTDEnum);
  public
    { Public declarations }
    function Add: TDTDEnum;
    function Find(const Name: string): TDTDEnum;
    property Items[Index: Integer]: TDTDEnum read GetItem write SetItem;
    default;
  end;

  { TDTDAttribute }

  TDTDAttribute = class(TDTDItem)
  private
    { Private declarations }
    FTyp: TDTDAttributeTyp;
    FStatus: TDTDAttributeStatus;
    FDefault: string;
    FEnums: TDTDEnums;
    procedure SetEnums(Value: TDTDEnums);
  public
    { Public declarations }
    constructor Create(Collection: TCollection); override;
    destructor Destroy; override;
    procedure Assign(Source: TPersistent); override;
  published
    { Published declarations }
    property Typ: TDTDAttributeTyp read FTyp write FTyp;
    property Status: TDTDAttributeStatus read FStatus write FStatus;
    property Default: string read FDefault write FDefault;
    property Enums: TDTDEnums read FEnums write SetEnums;
  end;

  { TDTDAttributes }

  TDTDAttributes = class(TDTDItems)
  private
    { Private declarations }
    function GetItem(Index: Integer): TDTDAttribute;
    procedure SetItem(Index: Integer; Value: TDTDAttribute);
  public
    { Public declarations }
    function Add: TDTDAttribute;
    function Find(const Name: string): TDTDAttribute;
    property Items[Index: Integer]: TDTDAttribute read GetItem write
    SetItem; default;
  end;

  { TDTDProperty }

  TDTDProperty = class(TDTDItem)
  private
    { Private declarations }
    FStatus: TDTDElementStatus;
  public
    { Public declarations }
    procedure Assign(Source: TPersistent); override;
  published
    { Published declarations }
    property Status: TDTDElementStatus read FStatus write FStatus;
  end;

  { TDTDProperties}

  TDTDProperties = class(TDTDItems)
  private
    { Private declarations }
    function GetItem(Index: Integer): TDTDProperty;
    procedure SetItem(Index: Integer; Value: TDTDProperty);
  public
    { Public declarations }
    function Add: TDTDProperty;
    function Find(const Name: string): TDTDProperty;
    property Items[Index: Integer]: TDTDProperty read GetItem write
    SetItem; default;
  end;

  { TDTDChild }

  TDTDChilds = class;

  TDTDChild = class(TDTDProperty)
  private
    { Private declarations }
    FTyp: TDTDChildTyp;
    FChilds: TDTDChilds;
    procedure SetChilds(const Value: TDTDChilds);
  public
    { Public declarations }
    constructor Create(Collection: TCollection); override;
    destructor Destroy; override;
    procedure Assign(Source: TPersistent); override;
  published
    { Published declarations }
    property Typ: TDTDChildTyp read FTyp write FTyp;
    property Childs: TDTDChilds read FChilds write SetChilds;
  end;

  { TDTDChilds}

  TDTDChilds = class(TDTDProperties)
  private
    { Private declarations }
    function GetItem(Index: Integer): TDTDChild;
    procedure SetItem(Index: Integer; Value: TDTDChild);
  public
    { Public declarations }
    function Add: TDTDChild;
    function Find(const Name: string): TDTDChild;
    property Items[Index: Integer]: TDTDChild read GetItem write SetItem;
    default;
  end;

  { TDTDElement }

  TDTDElement = class(TDTDProperty)
  private
    { Private declarations }
    FTyp: TDTDElementTyp;
    FAttributes: TDTDAttributes;
    FChilds: TDTDChilds;
    procedure SetAttributes(Value: TDTDAttributes);
    procedure SetChilds(Value: TDTDChilds);
  public
    { Public declarations }
    constructor Create(Collection: TCollection); override;
    destructor Destroy; override;
    procedure Assign(Source: TPersistent); override;
  published
    { Published declarations }
    property Typ: TDTDElementTyp read FTyp write FTyp;
    property Attributes: TDTDAttributes read FAttributes write
      SetAttributes;
    property Childs: TDTDChilds read FChilds write SetChilds;
  end;

  { TDTDElements }

  TDTDElements = class(TDTDProperties)
  private
    { Private declarations }
    function GetItem(Index: Integer): TDTDElement;
    procedure SetItem(Index: Integer; Value: TDTDElement);
  public
    { Public declarations }
    function Add: TDTDElement;
    function Find(const Name: string): TDTDElement;
    property Items[Index: Integer]: TDTDElement read GetItem write
    SetItem; default;
  end;

  { TDTDDocument }

  TDTDDocument = class(TPersistent)
  private
    { Private declarations }
    FEntities: TDTDEntities;
    FElements: TDTDElements;
    procedure SetEntities(Value: TDTDEntities);
    procedure SetElements(Value: TDTDElements);
  public
    { Public declarations }
    constructor Create;
    destructor Destroy; override;
    procedure Assign(Source: TPersistent); override;
  published
    { Published declarations }
    property Entities: TDTDEntities read FEntities write SetEntities;
    property Elements: TDTDElements read FElements write SetElements;
  end;

This model implements all needed objects to parse a dtd file. Notice, that not all dtd grammars are reflected in this model, it's up to you to improve my work - but it's enough to parse all standard dtd files.

Next, we need to create our dtd parser, which will be inherited by TValidationParser as professed in Part I:

type
  { EDTDParser }

  EDTDParser = class(Exception);

  { TDTDParser }

  TDTDParser = class(TValidationParser)
  private
    { Private declarations }
    procedure ParseElement(Parser: TStringParser; Document: TDTDDocument;
      const Pass: Integer);
    procedure ParseAttlist(Parser: TStringParser; Document: TDTDDocument);
    procedure ParseFile(const FileName: string; Document: TDTDDocument;
      const Pass: Integer = 0);
  public
    { Public declarations }
    procedure Parse(const FileName: string; var Document: TDTDDocument);
  end;

The new exception class EDTDParser will be raised, if the passed filename is physical not available. One of the weightily methods is Parse. The first parameter must be an existing filename of the dtd file to be parsed. The second parameter is the document which holds our object model and must be pre-initialized. The implementation of this  method is as followed:

01. procedure TDTDParser.Parse(const FileName: string; var Document: TDTDDocument);
02. var
03.   TmpDocument: TDTDDocument;
04. begin
05.   if not assigned(Document) then
06.     raise EDTDParser.Create('Document not assigned!');
07.   TmpDocument := TDTDDocument.Create;
08.   try
09.     ParseFile(FileName, TmpDocument);
10.     if Errors.Count = 0 then
11.       Document.Assign(TmpDocument);
12.   finally
13.     TmpDocument.Free;
14.   end;
15. end;

In Line 5 we're looking if the passed document was successfully initialized; if not, an exception (EDTDParser) will be raised. After comparing that, we create a new temporary instance of a dtd document (Line 7) and parse the passed filename (Line 9). If no errors occured (Line 10) we make a copy of the filled dtd document by assigning it to the passed one (Line 11).

Consecutively we take a look to the ParseFile procedure, which initializes the main parsing process and looks for the basic keywords: (Note: The italic lines are not part of the sourcecode - they are used to explain the unique sections)

procedure TDTDParser.ParseFile(const FileName: string;
  Document: TDTDDocument; const Pass: Integer = 0);
var
  Parser: TStringParser;
begin
  {Create a new instance of the TStringParser.}
  Parser := TStringParser.Create;
  try
    {Check, if the passed filename already exists.}
    if not Parser.LoadFromFile(FileName) then
    begin
      AddErrorFmt('File "%s" not found', [FileName], Parser);
      Exit;
    end;
    {Initialize an endless loop.}
    while True do
    begin
      {Skip to the next valid Tag-Begin-Token "<" or EOF.}
      while not (Parser.Token in [toEOF, '<']) do
        Parser.SkipToken;
      {Break look, if current Token is EOF - End of File.}
      if Parser.Token = toEOF then
        Break;
      {Get the next Token - after Tag-Begin "<".}
      Parser.SkipToken;
      {Check for valid identification Tag "!" or "?".}
      if Parser.Token <> '!' then
      begin
        {Only add an error if the current Pass is one "1".}
        if not (Parser.Token in ['?']) and (Pass = 1) then
          AddError('InvalidToken', Parser);
        Continue;
      end;
      {Check for valid Symbol or Comment Line.}
      if Parser.SkipToken <> toSymbol then
      begin
        if (Parser.Token <> '-') and (Pass = 1) then
          AddError('InvalidToken', Parser);
        Continue;
      end;
      {Check for "Entity" Tag.}
      if UpperCase(Parser.TokenString) = 'ENTITY' then
        Continue;
      {Check for "Element" Tag.}
      if UpperCase(Parser.TokenString) = 'ELEMENT' then
        ParseElement(Parser, Document, Pass)
      else
        {Check for "Attribute" Tag.} if UpperCase(Parser.TokenString) = 'ATTLIST' then
        begin
          if Pass = 1 then
            ParseAttlist(Parser, Document);
        end
          {Add an error on invalid Symbols.}
        else if Pass = 1 then
          AddErrorFmt('Invalid Symbol "%s"', [Parser.TokenString], Parser);
    end;
    {Initialize Pass 2 - if currently finished Pass 1.}
    if Pass = 0 then
      ParseFile(FileName, Document, 1);
  finally
    Parser.Free;
  end;
end;

The ParseFile method simply starts parsing the main structure of a dtd file and tries to extract some basic keywords like Entity, Element or Attribute. If one of the last two keywords were found, a special (ParseElement or ParseAttlist) method is called to create the corresponding object and to extract additional informations. If the parser founds any syntax or grammar errors, respectively items are created.

The method ParseElement includes the functionality to parse and extract further informations, like Type or Rule:
(Note: The italic lines are not part of the sourcecode - they are used to explain the unique sections)

procedure TDTDParser.ParseElement(Parser: TStringParser;
  Document: TDTDDocument; const Pass: Integer);
var
  Element: TDTDElement;
  Child: TDTDChild;
  Rule: string;
begin
  {Get the next Token.}
  Parser.SkipToken;
  {On first pass, create a new element.}
  if Pass = 0 then
    Element := Document.Elements.Add
      {On second pass, find previous created element.}
  else
    Element := Document.Elements.Find(Parser.TokenString);
  {Set the new element name.}
  Element.Name := Parser.TokenString;
  try
    {Add an error if the current Token isn't a symbol.}
    if Parser.Token <> toSymbol then
      Abort;
    {Check for element rule, like "any", "empty" or "sequence"...}
    Rule := UpperCase(Parser.SkipTokenString);
    {...Found Rule: "ANY".}
    if (Rule = 'ANY') and (Parser.SkipToken = '>') then
    begin
      Element.Typ := etAny;
      Exit;
    end;
    {...Found Rule: "EMPTY".}
    if (Rule = 'EMPTY') and (Parser.SkipToken = '>') then
    begin
      Element.Typ := etEmpty;
      Exit;
    end;
    if (Rule = '(') then
    begin
      {...Found Rule: "PCDATA".}
      if Parser.SkipToken in [toEOF, '>'] then
        Abort;
      if Parser.Token = '#' then
      begin
        if UpperCase(Parser.SkipToToken('>')) = 'PCDATA)' then
        begin
          Element.Typ := etData;
          Exit;
        end;
        Abort;
      end;
      {...Found Rule: "sequence/container".}
      Element.Typ := etContainer;
      repeat
        {Create Child objects, if pass = 1.}
        Child := nil;
        if not (Parser.Token in ['|', ',', ')']) then
        begin
          if Pass = 0 then
          begin
            Child := Element.Childs.Add;
            Child.Name := Parser.TokenString;
            Child.Typ := ctElement;
          end
          else if Document.Elements.Find(Parser.TokenString) = nil then
            AddErrorFmt('Invalid Element Target "%s"', [Parser.TokenString], Parser);
        end;
        Parser.SkipToken;
        {Check Child Status (=sequence style).}
        if Parser.Token in ['+', '?', '*'] then
        begin
          if Child <> nil then
            case Parser.Token of
              '+':
                Child.Status := esRequiredSeq;
              '?':
                Child.Status := esOptional;
              '*':
                Child.Status := esOptionalSeq;
            end;
          Parser.SkipToken;
        end;
      until Parser.SkipToken in [toEOF, '>'];
      Exit;
    end;
    {Add an error only on pass 1.}
    if Pass = 1 then
      AddErrorFmt('Invalid Element Rule "%s"', [Rule], Parser);
  except
    {Add an error only on pass 1.}
    if Pass = 1 then
      AddError('InvalidElementFormat', Parser);
  end;
end;

The method ParseAttlist includes the functionality to parse and extract further informations, like Type or Enumerations: (Note: The italic lines are not part of the sourcecode - they are used to explain the unique sections)

procedure TDTDParser.ParseAttlist(Parser: TStringParser; Document: TDTDDocument);
var
  Attribute: TDTDAttribute;
  Element: TDTDElement;
  Target, Typ: string;
begin
  {Get the next Token.}
  Target := Parser.SkipTokenString;
  try
    {Add an error if the current Token isn't a symbol.}
    if Parser.Token <> toSymbol then
      Abort;
    {Try to find the element target.}
    Element := Document.Elements.Find(Target);
    {Add an error if no element was found.}
    if Element = nil then
    begin
      AddErrorFmt('Invalid Element Target "%s"', [Target], Parser);
      Exit;
    end;
    {Get the next Token.}
    Parser.SkipToken;
    repeat
      {Add an error if the current Token isn't a symbol.}
      if Parser.Token <> toSymbol then
        Abort;
      {Create a new Attribute under the located element.}
      Attribute := Element.Attributes.Add;
      {Set the new name.}
      Attribute.Name := Parser.TokenString;
      {Check for Attribute Type...}
      Typ := Parser.SkipTokenString;
      {...Found Type "CDDATA".}
      if UpperCase(Typ) = 'CDATA' then
        Attribute.Typ := atData
      else
        {...Found Type "ID".} if UpperCase(Typ) = 'ID' then
          Attribute.Typ := atID
        else
          {...Found Type "IDREF".} if UpperCase(Typ) = 'IDREF' then
            Attribute.Typ := atIDRef
          else
            {...Found Type "enumeration".} if Typ = '(' then
            begin
              Attribute.Typ := atEnumeration;
              {Seperate enumeration parts and attach them}
              {to the parent attribute.}
              repeat
                Parser.SkipToken;
                if not (Parser.Token in ['|', ')']) then
                  Attribute.Enums.Add.Name := Parser.TokenString;
              until Parser.Token in [toEOF, ')'];
              {Add an error, if current token is "EOF".}
              if Parser.Token = toEOF then
              begin
                AddErrorFmt('Invalid Enumeration End in Attribute "%s"',
                  [Attribute.Name], Parser);
                Exit;
              end;
            end
            else
            begin
              AddErrorFmt('Invalid Attribute Typ "%s"', [Typ], Parser);
              Exit;
            end;
      {Check for Restrictions...}
      Parser.SkipToken;
      if Parser.Token = '#' then
      begin
        {...Found Restriction "IMPLIED".}
        Typ := UpperCase(Parser.SkipTokenString);
        if Typ = 'IMPLIED' then
        begin
          Attribute.Status := asImplied;
          Parser.SkipToken;
        end;
        {...Found Restriction "REQUIRED".}
        if Typ = 'REQUIRED' then
        begin
          Attribute.Status := asRequired;
          Parser.SkipToken;
        end;
        {...Found Restriction "FIXED".}
        if Typ = 'FIXED' then
        begin
          Attribute.Status := asFixed;
          Parser.SkipToken;
        end;
      end;
      {Extract an optional default value.}
      if Parser.Token = '"' then
      begin
        if Attribute.Status = asImplied then
          Abort;
        Attribute.Default := Trim(Parser.SkipToToken('"'));
        Parser.SkipToken;
      end;
    until Parser.Token = '>';
  except
    AddErrorFmt('Invalid Attribute Format "%s"', [Target], Parser);
  end;
end;

Note: The above methods only detects simple dtd grammas. To parse all possible tags and additional grammars you had to include a more complex algorithm to do that - for our purposes (and this article) it's enough. If you are not familiar with the dtd syntax, check out the site W3Schools.

Okay, at this point we have finished our object-model and parser implementation. All we need now is an example application which will take use of this units. Our demo application will parse a dtd file, detects the structure and creates a simple xml output with a given startup node. Take a look at the following dtd:

<!ELEMENT Extension EMPTY>
<!ATTLIST Extension
name CDATA #REQUIRED
value CDATA #REQUIRED
>
<!ELEMENT Code (#PCDATA)>
<!ELEMENT Message (#PCDATA)>
<!ELEMENT Status (Code, Message?)>
<!ATTLIST Status
Type (Error | Warning | Information) #REQUIRED
>
<!ELEMENT BekoId (#PCDATA)>
<!ELEMENT BeraBeratungID (#PCDATA)>
<!ELEMENT BeratungsKontextResp (BekoId, BeraBeratungID, Status, Extension*)>

Our demo application will create the following xml output:

<?xml version='1.0'?>
<!DOCTYPE BeratungsKontextResp SYSTEM 'sample.dtd'>

<BeratungsKontextResp>
  <BekoId></BekoId>
  <BeraBeratungID></BeraBeratungID>
  <Status Type="">
    <Code></Code>
    <Message></Message>
  </Status>
  <Extension name="" value=""></Extension>
</BeratungsKontextResp>

In this case, the startup node is BeratungsKontextResp which will be used as the root node for all other nodes. Our example is implemented as a console application as followed:

program dtd2xml;

{$APPTYPE CONSOLE}

uses
  SysUtils,
  DTD_Parser in 'DTD_Parser.pas',
  DTD_Document in 'DTD_Document.pas',
  StringParser in 'StringParser.pas',
  PrivateParser in 'PrivateParser.pas';

var
  FileName: string;
  Switch_XMLRoot: string;
  Switch_XMLData: Boolean;
  Switch_RootLst: Boolean;
  DTDDocument: TDTDDocument;
  DTDParser: TDTDParser;
  RootElement: TDTDElement;
  i: Integer;

  {-----------------------------------------------------------------------------
    Procedure: FindCmdSwitch
    Author:    mh
    Date:      23-Jan-2002
    Arguments: const Switch: string; const Default: string = ''
    Result:    string
  -----------------------------------------------------------------------------}

function FindCmdSwitch(const Switch: string; const Default: string = ''): string;
var
  i: Integer;
begin
  Result := '';
  for i := 1 to ParamCount do
    if UpperCase(Copy(ParamStr(i), 1, Length(Switch))) = UpperCase(Switch) then
    begin
      Result := Copy(ParamStr(i), Length(Switch) + 1, MAXINT);
      Exit;
    end;
  if Result = '' then
    Result := Default;
end;

{-----------------------------------------------------------------------------
  Procedure: WriteXML
  Author:    mh
  Date:      23-Jan-2002
  Arguments: const AElement: TDTDElement; const AStatus: TDTDElementStatus; Indent: Integer = 0
  Result:    None
-----------------------------------------------------------------------------}

procedure WriteXML(const AElement: TDTDElement; const AStatus: TDTDElementStatus;
  Indent: Integer = 0);
var
  i: Integer;
  Spacer, Def: string;
begin
  for i := 1 to Indent * 2 do
    Spacer := Spacer + #32;
  Write(Spacer + '<' + AElement.Name);
  for i := 0 to AElement.Attributes.Count - 1 do
    with AElement.Attributes[i] do
    begin
      Def := Default;
      if (Switch_XMLData) and (Def = '') then
      begin
        if Typ = atEnumeration then
        begin
          if Enums.Count > 0 then
            Def := Enums[0].Name
          else
            Def := '???';
        end
        else
          Def := Name;
      end;
      Write(Format(' %s="%s"', [Name, Def]));
    end;
  if AElement.Typ <> etContainer then
  begin
    Def := '';
    if (Switch_XMLData) and (AElement.Typ <> etEmpty) then
      Def := AElement.Name;
    WriteLn(Format('>%s', [Def, AElement.Name]));
  end
  else
    WriteLn('>');
  for i := 0 to AElement.Childs.Count - 1 do
    WriteXML(DTDDocument.Elements.Find(AElement.Childs[i].Name),
      AElement.Childs[i].Status, Indent + 1);
  if AElement.Typ = etContainer then
    WriteLn(Spacer + Format('', [AElement.Name]));
end;

{-----------------------------------------------------------------------------
  Procedure: main
  Author:    mh
  Date:      23-Jan-2002
  Arguments: None
  Result:    None
-----------------------------------------------------------------------------}
begin
  // display usage.
  if (ParamCount = 0) or (FindCmdSwitch('-?', '?') <> '?') then
  begin
    WriteLn('');
    WriteLn('dtd2xml (parser framework example) version 1.0');
    WriteLn('(w)ritten 2002 by Marc Hoffmann. GNU License');
    WriteLn('');
    WriteLn('Usage: dtd2xml [options] [-?]');
    WriteLn('');
    WriteLn('Options:');
    WriteLn('-xmlroot=           XML root element (? = possible elements)');
    WriteLn('-xmldata=yes|no           Include XML Example data (default = yes)');
    WriteLn('');
    Exit;
  end;

  // exract filename.
  FileName := ParamStr(1);

  // append default extenstion,
  if ExtractFileExt(FileName) = '' then
    FileName := ChangeFileExt(FileName, '.dtd');

  // file exists?
  if not FileExists(FileName) then
  begin
    WriteLn(Format('Fatal: File not found ''%s''.', [FileName]));
    Exit;
  end;

  // extract command-line switches.
  Switch_RootLst := FindCmdSwitch('-xmlroot=') = '?';
  Switch_XMLRoot := FindCmdSwitch('-xmlroot=');
  Switch_XMLData := UpperCase(FindCmdSwitch('-xmldata=')) <> 'NO';

  // create new dtd-document.
  DTDDocument := TDTDDocument.Create;
  try
    // create new dtd-parser.
    DTDParser := TDTDParser.Create;
    try
      // parse file.
      DTDParser.Parse(FileName, DTDDocument);

      // display possible errors.
      if DTDParser.Errors.Count > 0 then
      begin
        for i := 0 to DTDParser.Errors.Count - 1 do
          with DTDParser.Errors[i] do
            WriteLn(Format('Error in Line %d, Pos %d: %s...', [Line, Position,
              Message]));
        Exit;
      end;

      // search rootelement.
      RootElement := DTDDocument.Elements.Find(Switch_XMLRoot);

      // display rootelements & assign possible object.
      for i := 0 to DTDDocument.Elements.Count - 1 do
        if DTDDocument.Elements[i].Typ = etContainer then
        begin
          if Switch_RootLst then
            WriteLn(DTDDocument.Elements[i].Name)
          else if (Switch_XMLRoot = '') and ((RootElement = nil) or ((RootElement <>
            nil)
            and (RootElement.Childs.Count < DTDDocument.Elements[i].Childs.Count)))
              then
            RootElement := DTDDocument.Elements[i];
        end;

      // exit app if rootlist-switch was set.
      if Switch_RootLst then
        Exit;

      // exit app if rootelement is NIL.
      if RootElement = nil then
      begin
        WriteLn(Format('Fatal: Root Element ''%s'' not found.', [Switch_XMLRoot]));
        Exit;
      end;

      // exit app if rootelement is invalid.
      if RootElement.Typ <> etContainer then
      begin
        WriteLn(Format('Fatal: ''%s'' is not a valid Root Element.',
          [Switch_XMLRoot]));
        Exit;
      end;

      // write xml output.
      WriteLn(Format('' + #13 + '', [RootElement.Name, ExtractFileName(FileName)]));
      WriteLn('');
      WriteXML(RootElement, RootElement.Status);

      // free dtd-parser.
    finally
      DTDParser.Free;
    end;

    // free dtd-document.
  finally
    DTDDocument.Free;
  end;
end.

Nincsenek megjegyzések:

Megjegyzés küldése