Archive

Archive for September, 2013

Delphi xml data binding pains

September 17, 2013 6 comments
xml structures

xml structures

Delphi ships with an XML data binding wizard that is, perhaps, not the most widely used utility in the Delphi toolbox but truly one of the most valuable. It really is a shame that it’s not more widely used because it can really simplify working with xml in all it’s forms. For instance, if you have a custom fileformat for your project or preferences files, then sculpting that as a schema in something like XML Spy – and then getting Delphi to generated easy to use classes to access the file is very simple.

But there are problems. Serious problems if you want to use the XML data binding wizard for anything complex and professional.

The current problem is that the import wizard screws-up namespaces. As you probably know an XSD file allows you to not only sculpt the structure of an XML file – it also allows you to pre-define data structures. Think of it as a schema that can contain delphi record definitions. An XSD file can also refer to other XSD files which is not unlike the dependencies pascal has for units, where we declare the list of units we need in the uses clause.

And this is where the XML data binding wizard simply ruins the whole game. In my example we have a huge set of XSD files that are all inter-linked and defines a large set of data structures. These structures are defined by the government – so we really dont have a choice in the matter. The data binding wizard imports everything correctly and it also generates code that matches the data structures. But it forgets to prefix various datatypes with the correct namespace token.

Example: Let’s say you have an XSD file that defines personal information. Stuff like name, address, an array of phone numbers, array of email or PM services etc.
This file imports a second file where all the datatypes are defined. So the address record etc. are defined in this second file, but put into a more meaningful structure in the first file. This is just like declaring your records and types in one unit and using them from a second unit in pascal.

The problem? XML requires you to prefix external datatypes so the parser can tell the difference. One example is a structure called “id” which defines an array of “id” items. This is perfectly valid as long as you prefix the array items correctly:

<?xml version="1.0" encoding="utf-8"?>
<MyRecord xmlns="root namespace" xmlns:tnsa="second namespace">
<id>
  <tnsa:id>
    < .. >
  </tnsa:id>
  <tnsa:id>
    < .. >
  </tnsa:id>
</id>
</MyRecord>

In the above pseudo xml output, we see that the first “id” tag has no prefix, it defaults to the root namespace. Whenever datatypes declared in “second namespace” is used, it should be prefixed with “tnsa” as defined. But the Delphi data binding wizard simply ignores this and doesn’t prefix any of the generated tags. Instead we get this:

<?xml version="1.0" encoding="utf-8"?>
<MyRecord xmlns="root namespace" xmlns:tnsa="second namespace">
<id>
  <id>
    < .. >
  </id>
  <id>
    < .. >
  </id>
</id>
</MyRecord>

Which causes a natural error because any parser that receives the generated XML will think the sub-items are of the same type as that defined in the root schema, causing a rather nasty validation exception.

It really sucks when you have to “roll your own” data binding for a product costing 10 times more than it’s competitors.┬áThis bug has been here since Delphi 7 – it really is about time the codegen was updated to correctly prefix the generated data. I was able to use the free version of visual studio to generate a C# databinding out of the box correctly, something that made me very sad for the product we all love.

Handle your tabs

September 3, 2013 Leave a comment

Here is a small class that makes handling TAB pages (TTabSheets) easier. Especially if you make IDE applications (or applications that present information in separate tabs). It allows you to connect a tabsheet to an object – which may or may not be a sourcecode object. The addTabFile and addTabSource are just examples of how you can extend the basic functionality to keep track of source code editors and link them back to a project element, for instance.

Tabs galore, easy to handle

Tabs galore, easy to handle

Useful and handy.


  TTabSourceType = (stCustom,stSourceFile, stSource);

  TTabElement = Class(TObject)
  private
    FKind:      TTabSourceType;
    FSheet:     TTabSheet;
    FFilename:  String;
    FSource:    TAdvMemoSource;
    FRef:       TObject;
  public
    Property    Source:TAdvMemoSource read FSource write FSource;
    Property    Filename:String read FFilename write FFilename;
    Property    Sheet:TTabSheet read FSheet write FSheet;
    Property    Kind:TTabSourceType read FKind write FKind;
    Property    RefObj:TObject read FRef write FRef;
  End;

  TTabController = Class(TObject)
  private
    FPage:      TPageControl;
  public
    Property  PageControl:TPageControl read FPage;

    function  AddTab(const aType:TTabSourceType;
              const aRef:TObject;
              out aObj: TTabElement):Boolean;

    function  AddTabFile(aFilename:String;
              const aRef:TObject;
              out aObj:TTabElement):Boolean;

    function  AddTabSource(const aRef:TObject;
              out aObj:TTabElement):Boolean;

    function  getTabByRef(const aRef:TObject;
              out aTab:TTabElement):boolean;

    Constructor Create(aPageControl:TPageControl);virtual;
  End;

//############################################################################
// TTabController
//############################################################################

Constructor TTabController.Create(aPageControl:TPageControl);
Begin
  inherited Create;
  FPage:=aPageControl;
end;

function TTabController.AddTabFile(aFilename:String;
         const aRef:TObject;
         out aObj:TTabElement):Boolean;
var
  mObj:     TTabElement;
Begin
  result:=False;
  aObj:=NIL;
  if FileExists(aFilename,true) then
  Begin
    if AddTabSource(aRef,mObj) then
    Begin
      mObj.Kind:=stSourceFile;
      mObj.Sheet.Caption:=ExtractFileName(aFilename);
      mObj.Filename:=aFilename;
      mObj.Source.Lines.LoadFromFile(aFilename);
      aObj:=mObj;
      result:=true;
    end;
  end;
end;

function TTabController.AddTabSource(Const aRef:TObject;
         out aObj:TTabElement):Boolean;
var
  mObj:     TTabElement;
  mMemo:    TAdvMemo;
  mSource:  TAdvMemoSource;
  mStyler:  TAdvPascalMemoStyler;
Begin
  result:=False;
  aObj:=NIL;
  if AddTab(stSource,aRef,mObj) then
  Begin
    (* Create memo *)
    mMemo:=TAdvMemo.Create(mObj.Sheet);
    mMemo.Parent:=mObj.Sheet;
    mMemo.Align:=alClient;

    (* create styler control *)
    mStyler:=TAdvPascalMemoStyler.Create(mObj.Sheet);

    (* create source control *)
    mSource:=TAdvMemoSource.Create(mObj.Sheet);
    mSource.SyntaxStyler:=mStyler;

    (* Link source to memo *)
    mMemo.MemoSource:=mSource;

    (* glue together and success *)
    mObj.Source:=mSource;
    aObj:=mObj;
    result:=true;
  end;
end;

function TTabController.AddTab(const aType:TTabSourceType;
              const aRef:TObject;
              out aObj: TTabElement):Boolean;
var
  mTab: TTabSheet;
begin
  result:=False;
  aObj:=nil;

  try
    mTab:=TTabSheet.Create(FPage);
    mTab.PageControl:=FPage;
    mTab.Visible:=True;
    mTab.Show;
  except
    on exception do
    exit;
  end;

  aObj:=TTabElement.Create;
  aObj.Sheet:=mTab;
  aObj.Kind:=aType;
  aObj.RefObj:=aRef;
  mTab.Tag:=NativeInt(aObj);
  Result:=True;
end;

function TTabController.getTabByRef(const aRef:TObject;
              out aTab:TTabElement):boolean;
var
  x:      Integer;
  mPage:  TTabSheet;
  mObj:   TTabElement;
begin
  result:=False;
  aTab:=NIL;
  if (FPage<>NIL)
  and not (csDestroying in FPage.ComponentState) then
  Begin
    for x:=0 to FPage.PageCount-1 do
    Begin
      mPage:=FPage.Pages[x];
      if mPage.Tag<>0 then
      Begin
        mObj:=TTabElement(mPage.Tag);
        if mObj.RefObj=aRef then
        Begin
          result:=True;
          aTab:=mObj;
          Break;
        end;
      end;
    end;
  end;
end;