2005. november 12., szombat

XML File Viewer


Problem/Question/Abstract:

How to create a simple XML File viewer, without worrying about the XML itself. The easiest solution is using the Microsoft XML Document Object Model, installed on every machine that run MS Internet Explorer 4 or higher.

Answer:

In this article I am showing you a simple XML file viewer, that may be extended to an XML editor with some work. You may enhance the editor by importing icons for the different node types, etc. That's up to you.

My main idea is to show you how to import type libraries from readily available components installed on nearly every machine in the modern windows world.

Note: The MS XML DOM is available for free download and redistribution at msdn.microsoft.com

IMPORTING THE MS XML TYPE LIBRARY

Start Delphi and create a new application, I you haven't done so already. In the Delphi menu go to Project|Import Type Library... A dialog will appear on your screen, with a list of all installed and registered COM libraries available for import. Take a moment and scroll through it, you might be surprised.

Somewhere down the list you find Microsoft XML, version 2.0 (Version 2.0). This is the type library we are going to import. Additionally, you may see Microsoft XML, v3.0 (Version 3.0). This is a newer and faster version from MS, we are going to use the older version however, since it is more common.

After selecting the MS XML, version 2.0 component object, select a Unit Directory, and press the Create Unit button. The Install button will install the component in your Component Pallete, additionally.

PREPARING YOUR APPLICATION FORM

Drop a MainMenu (Standard) component on your form, and insert a Open Menu item (name: Open1).
Drop a TreeView (Win32) component on your form, set Align=alLeft and ReadOnly=True (name: trvStructure).
Drop an OpenDialog (Dialogs) component on your form (name: OpenDialog1).
Drop a Panel (Standard) component on your form, set Align=alClient and clear the Caption (name: Panel1).
Drop a StringGrid (Additional) component on the Panel1 set Align=alTop, RowCount=2, ColCount=2, FixedCols=0, FixedRows=1 (name: grdAttributes).
Drop a Memo (Standard) on the Panel1 set Align=alClient, ReadOnly=True (name mmoNodeContent).

Note: The names I have used will appear in the source code again!

A PSEUDO CLASS FOR THE XML INTERFACES

Because mapping of interface via pointers introduces some problems I chose to create a simple class that contains only on variable holding the reference to the XML Node interface.

type
  TXMLNodeWrapper = class
  private
    FNode: IXMLDOMNode;
  protected
  public
    constructor Create(aNode: IXMLDOMNode);
    property Node: IXMLDOMNode read FNode;
  end;

The constructor will save the reference in the FNode variable.

CREATING THE XML DOM OBJECT

Creating an instance of the object is rather simple. Having a variable FDocument of the type IXMLDOMDocument, defined in the imported MSXML_TLB.

FDocument := CoDOMDocument.Create;

Next you need to set up the component to your needs.

FDocument.async := False;
FDocument.validateOnParse := True;
FDocument.preserveWhiteSpace := True;

The first I want to do is inserting an base element into the document. Every XML document needs at least this base element. I have named it xmlstart.

Note: Be careful, XML is case-sensitive.

FDocument.appendChild(FDocument.createNode(NODE_ELEMENT, 'xmlstart', ''));

PARSING THE XML DOCUMENT

There are quite many ways of parsing XML. I want to show you two recursive ways, that are very similar, but have quite different results.

(1) NodeList := Node.childNodes;

Returns all children, inlcude some special node types, such as #text or #comment. These node types require special care.

(2) NodeList := Node.selectNodes('*');

Returns all standard node types, that can be accessed via XSL (XML Structured Language). These node types are easy in use.

ACCESSING THE NODE LIST

Accessing any item in a Node List is very easy. The length returns the count of items in the list (equal to Delphis Count property). The Item array gives access to every Item of the node list.

for I := 0 to Pred(XMLNodeList.length) do
  ShowMessage(XMLNodeList.item[I].nodeName);

MORE INFORMATION ABOUT THE MS XML DOM

The most important web addresses for the MS XML DOM are:

http://msdn.microsoft.com/xml  (all about XML)
http://msdn.microsoft.com/downloads/default.asp?URL=/code/topic.asp?URL=/msdn-files/028/000/072/topic.xml (Downloads)

THE SOURCE CODE FOR THE XML VIEWER

unit uMainForm;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  MSXML_TLB, ComCtrls, Menus, Grids, ExtCtrls, StdCtrls;

type
  TXMLNodeWrapper = class
  private
    FNode: IXMLDOMNode;
  protected
  public
    constructor Create(aNode: IXMLDOMNode);
    property Node: IXMLDOMNode read FNode;
  end;

  TfrmMain = class(TForm)
    MainMenu1: TMainMenu;
    File1: TMenuItem;
    Open1: TMenuItem;
    trvStructure: TTreeView;
    OpenDialog1: TOpenDialog;
    Panel1: TPanel;
    grdAttributes: TStringGrid;
    mmoNodeContent: TMemo;
    procedure FormCreate(Sender: TObject);
    procedure Open1Click(Sender: TObject);
    procedure trvStructureChange(Sender: TObject; Node: TTreeNode);
  private
    FDocument: IXMLDOMDocument;
    FFileName: string;
    procedure LoadXML;
  public
  end;

var
  frmMain: TfrmMain;

implementation

{$R *.DFM}

{ TXMLNodeWrapper }

constructor TXMLNodeWrapper.Create(aNode: IXMLDOMNode);
begin
  inherited Create;
  FNode := aNode;
end;

{ TFrmMain }

procedure TfrmMain.FormCreate(Sender: TObject);
begin
  FDocument := CoDOMDocument.Create;
  FDocument.async := False;
  FDocument.validateOnParse := True;
  FDocument.preserveWhiteSpace := True;
  FDocument.appendChild(FDocument.createNode(NODE_ELEMENT, 'xmlstart', ''));

  grdAttributes.Cells[0, 0] := 'Attribute name';
  grdAttributes.Cells[1, 0] := 'Attribute value';
end;

procedure TfrmMain.LoadXML;
  procedure EnterNode(const XMLNode: IXMLDOMNode; TreeNode: TTreeNode);
  var
    I: Integer;
    XMLNodeList: IXMLDOMNodeList;
    NewTreeNode: TTreeNode;
  begin
    NewTreeNode := trvStructure.Items.AddChild(TreeNode, XMLNode.nodeName);
    NewTreeNode.Data := TXMLNodeWrapper.Create(XMLNode);
    // use XMLNode.childNodes to get all nodes (incl. special types)
    XMLNodeList := XMLNode.selectNodes('*');
    for I := 0 to Pred(XMLNodeList.length) do
      EnterNode(XMLNodeList.item[I], NewTreeNode);
  end;
begin
  for I := 0 to trvStructure.Items.Count - 1 do
    TXMLNodeWrapper(trvStructure.Items.Item[I].Data).Destroy;
  trvStructure.Items.BeginUpdate;
  try
    trvStructure.Items.Clear;
    EnterNode(FDocument.documentElement, nil);
  finally
    trvStructure.Items.EndUpdate;
  end;
end;

procedure TfrmMain.Open1Click(Sender: TObject);
begin
  if OpenDialog1.Execute then
  begin
    FDocument.load(OpenDialog1.FileName);
    FFileName := OpenDialog1.FileName;
    LoadXML;
  end;
end;

procedure TfrmMain.trvStructureChange(Sender: TObject; Node: TTreeNode);
var
  I: Integer;
  CurrentNode: IXMLDOMNode;
begin
  CurrentNode := TXMLNodeWrapper(Node.Data).Node;
  Caption := CurrentNode.nodeName;
  if CurrentNode.selectNodes('*').length = 0 then
    mmoNodeContent.Text := CurrentNode.text
  else
    mmoNodeContent.Text := '';
  if CurrentNode.attributes.length > 0 then
  begin
    grdAttributes.RowCount := Succ(CurrentNode.attributes.length);
    grdAttributes.FixedRows := 1;
    for I := 0 to Pred(CurrentNode.attributes.length) do
    begin
      grdAttributes.Cells[0, Succ(I)] := CurrentNode.attributes.item[I].nodeName;
      grdAttributes.Cells[1, Succ(I)] := CurrentNode.attributes.item[I].text;
    end;
  end
  else
  begin
    grdAttributes.RowCount := 2;
    grdAttributes.Cells[0, 1] := '';
    grdAttributes.Cells[1, 1] := '';
  end;
end;

end.

Nincsenek megjegyzések:

Megjegyzés küldése