unit ufXmlFormat;
{$INCLUDE 'Filters.inc'}

interface

uses
  SysUtils, Classes,
  uXml,
  uFilter, uMessages;

type
  TFilterXmlFormat = class(TFilter)
  protected
    function LoadContent(const Content: string): TXmlDoc;
  public
    class function ID: string; override;
    class function Name: string; override;
    class function Priority: TFilter.TPriority; override;
    procedure Clear; override;
    function IsSupported(const FileName, Content: string): boolean; override;
    function Execute(const FileName, Content: string): string; override;
  end;

implementation

{ TFilterXmlFormat }

procedure TFilterXmlFormat.Clear;
begin
  inherited;
  Description := GetMsg(MFilterXmlFormatDescription);
  FileMasks.Add('*.xml');
  FileMasks.Add('*.xsd');
  FileMasks.Add('*.xslt');
  FileMasks.Add('*.wsdl');
  FileMasks.Add('*.xhtml');
end;

function TFilterXmlFormat.Execute(const FileName, Content: string): string;
var
  Xml: TXmlDoc;
begin
  Xml := LoadContent(Content);
  try
    Xml.SetIndentation('  ');
    Result := string(Xml.WriteToString);
  finally
    FreeAndNil(Xml);
  end;
end;

class function TFilterXmlFormat.ID: string;
begin
  Result := 'xml';
end;

function TFilterXmlFormat.IsSupported(const FileName, Content: string): boolean;
type
  TState = (stStart, stBOM, stStartingWhiteSpace);
  TSupported = (suUnknown, suNo, suYes);
var
  Supported: TSupported;
  State: TState;
  i, n: integer;
begin
  Result := False;
  if inherited IsSupported(FileName, Content) then
  begin
    try
      Supported := suUnknown;
      n := Length(Content);
      // File must start with an optional BOM, optional whitespace, '<'
      begin
        State := stStart;
        for i := 1 to n do
        begin
          case Ord(Content[i]) of
            $feff:
              if State = stStart then
                State := stBOM
              else
                Supported := suNo;
            0, 9, 10, 13, 32:
              State := stStartingWhiteSpace;
            Ord('<'):
              Supported := suYes;
            else
              Supported := suNo;
          end;
          if Supported <> suUnknown then
            Break;
        end;
      end;
      // File must end with '>', optional whitespace
      if Supported = suYes then
      begin
        Supported := suUnknown;
        for i := n downto 1 do
        begin
          case Ord(Content[i]) of
            0, 9, 10, 13, 32:
              ;
            Ord('>'):
              Supported := suYes;
            else
              Supported := suNo;
          end;
          if Supported <> suUnknown then
            Break;
        end;
      end;
      Result := (Supported = suYes);
    except
      Result := False;
    end;
  end;
end;

function TFilterXmlFormat.LoadContent(const Content: string): TXmlDoc;
begin
  Result := TXmlDoc.Create;
  try
    Result.ReadFromString(TXmlString(Content));
  except
    FreeAndNil(Result);
    Raise;
  end;
end;

class function TFilterXmlFormat.Name: string;
begin
  Result := GetMsg(MFilterXmlFormatName);
end;

class function TFilterXmlFormat.Priority: TFilter.TPriority;
begin
  Result := prLow;
end;

initialization
  TRegisteredFilters.Add(TFilterXmlFormat);

end.
