Search code examples
xmldelphixfdf

How to properly create XML header (to fill pdf documents in xfdf format) with Delphi using IXMLDOCUMENT


please I need help to achieve this xml, I have a problme with the third line, this is what I get with my code:

<?xml version="1.0" encoding="utf-8"?>
<xfdf xmlns="http://ns.adobe.com/xfdf/" xml:space="preserve">
  <f xmlns="" href="myExample.pdf">
    <fields>
      <field name="chk01">
        <value>X</value>
      </field>
      <field name="chk02">
        <value>X</value>
      </field>
      <field name="edt11">
        <value>Some text</value>
      </field>
    </fields>
  </f>
</xfdf>

and this is what I need:

<?xml version="1.0" encoding="UTF-8"?>
<xfdf xmlns="http://ns.adobe.com/xfdf/" xml:space="preserve">    
  <f href="myExample.pdf"/>
  <fields>
    <field name="chk01">
      <value>X</value>
    </field>
    <field name="chk02">
      <value>X</value>
    </field>
    <field name="edt11">
      <value>Some text</value>
    </field>
  </fields>
</xfdf>

I dont know how to create the "f" tag like in the second example, notice that it's a little bit different and it closes in the same line and not before the last line like in the first example.

Here is my code:

....
Var
  XML : IXMLDOCUMENT;
  RootNode, NodeLevel1, CurNode : IXMLNODE;
Begin
  XML := NewXMLDocument;
  XML.Encoding := 'utf-8';
  XML.Options := [doNodeAutoIndent];

  RootNode := XML.AddChild('xfdf');
  RootNode.Attributes['xmlns']:= 'http://ns.adobe.com/xfdf/';
  RootNode.Attributes['xml:space']:= 'preserve';

  RootNode := XML.DocumentElement.AddChild('f');
  RootNode.Attributes['href']:= 'myExample.pdf';

  NodeLevel1 := RootNode.AddChild('fields');
  CurNode := NodeLevel1.AddChild('field');
  CurNode.Attributes['name'] := 'chk01';
  CurNode := CurNode.AddChild('value');
  CurNode.Text:= 'X';

  CurNode := NodeLevel1.AddChild('field');
  CurNode.Attributes['name'] := 'chk02';
  CurNode := CurNode.AddChild('value');
  CurNode.Text:= 'X';

  CurNode := NodeLevel1.AddChild('field');
  CurNode.Attributes['name'] := 'edt11';
  CurNode := CurNode.AddChild('value');
  CurNode.Text:= 'Some text';

 XMl.SaveToFile('C:\New.fdf');

Solution

  • You are getting a bad result because:

    1. you are not handling XML namespaces correctly.

    2. you are creating the fields node as a child of the f node instead of as a child of the xfdf node.

    When a child node is added, it inherits its parent node's namespace unless you explicitly specify a different namespace, and the namespace cannot be changed once assigned.

    In your case, your xfdf node is not actually being added to the Adobe namespace, you are faking it! The XML engine does not know about the namespace, so the xfdf node has a blank namespace, which the f child node inherits. That is why the f node ends up with the unwanted xmlns="" attribute, so there is no ambiguity as it which namespace child nodes belong to.

    To declare a namespace correctly so the XML engine knows about it, you need to use the IXMLNode.DeclareNamespace() method instead of using the IXMLNode.Attributes property.

    You can either:

    1. explicitly declare the Adobe namespace on the xfdf node after adding it to the document. The xfdf node itself will not be in the namespace (as evident by the IXMLNode.NamespaceURI property still being blank), but the namespace can then be explicitly applied when adding the f and fields child nodes:

      const
        NsAdobeXfdf = 'http://ns.adobe.com/xfdf/';
      var
        XML: IXMLDocument;
        RootNode, FNode, FieldsNode, FieldNode: IXMLNode;
      begin
        XML := NewXMLDocument;
        XML.Encoding := 'utf-8';
        XML.Options := [doNodeAutoIndent];
      
        RootNode := XML.AddChild('xfdf'); // <-- xfdf is not in any namespace
        RootNode.DeclareNamespace('', NsAdobeXfdf); // <-- declare the namespace
        RootNode.Attributes['xml:space'] := 'preserve';
      
        FNode := RootNode.AddChild('f', NsAdobeXfdf); // <-- f is in the Adobe namespace
        FNode.Attributes['href'] := 'myExample.pdf';
      
        FieldsNode := RootNode.AddChild('fields', NsAdobeXfdf); // <-- fields is in the Adobe namespace
      
        FieldNode := FieldsNode.AddChild('field'); // <-- inherits namespace from fields
        FieldNode.Attributes['name'] := 'chk01';
        FieldNode.AddChild('value').Text := 'X'; // <-- inherits namespace from field
      
        FieldNode := FieldsNode.AddChild('field'); // <-- inherits namespace from fields
        FieldNode.Attributes['name'] := 'chk02';
        FieldNode.AddChild('value').Text := 'X'; // <-- inherits namespace from field
      
        FieldNode := FieldsNode.AddChild('field'); // <-- inherits namespace from fields
        FieldNode.Attributes['name'] := 'edt11';
        FieldNode.AddChild('value').Text := 'Some text'; // <-- inherits namespace from field
      
        XML.SaveToFile('C:\New.fdf');
      end;
      
    2. alternatively, declare the Adobe namespace on the xfdf node at the time you add that node to the document, and then that node will be in the namespace so the f and fields child nodes inherit it without you having to call DeclareNamespace() manually (AddChild() calls it internally for you):

      const
        NsAdobeXfdf = 'http://ns.adobe.com/xfdf/';
      var
        XML: IXMLDocument;
        RootNode, FNode, FieldsNode, FieldNode: IXMLNode;
      begin
        XML := NewXMLDocument;
        XML.Encoding := 'utf-8';
        XML.Options := [doNodeAutoIndent];
      
        RootNode := XML.AddChild('xfdf', NsAdobeXfdf); // <-- xfdf is in the Adobe namespace
        RootNode.Attributes['xml:space'] := 'preserve';
      
        FNode := RootNode.AddChild('f'); // <-- inherits namespace from xfdf
        FNode.Attributes['href'] := 'myExample.pdf';
      
        FieldsNode := RootNode.AddChild('fields'); // <-- inherits namespace from xfdf
      
        FieldNode := FieldsNode.AddChild('field'); // <-- inherits namespace from fields
        FieldNode.Attributes['name'] := 'chk01';
        FieldNode.AddChild('value').Text := 'X'; // <-- inherits namespace from field
      
        FieldNode := FieldsNode.AddChild('field'); // <-- inherits namespace from fields
        FieldNode.Attributes['name'] := 'chk02';
        FieldNode.AddChild('value').Text := 'X'; // <-- inherits namespace from field
      
        FieldNode := FieldsNode.AddChild('field'); // <-- inherits namespace from fields
        FieldNode.Attributes['name'] := 'edt11';
        FieldNode.AddChild('value').Text := 'Some text'; // <-- inherits namespace from field
      
        XML.SaveToFile('C:\New.fdf');
      end;
      

    Either approach will remove the xmlns="" attribute on the f and fields nodes since they belong to a namespace that is declared in the xfdf node, and subsequent child nodes will inherit the namespace as expected:

    <?xml version="1.0" encoding="utf-8"?>
    <xfdf xmlns="http://ns.adobe.com/xfdf/" xml:space="preserve">
      <f href="myExample.pdf"/>
      <fields>
        <field name="chk01">
          <value>X</value>
        </field>
        <field name="chk02">
          <value>X</value>
        </field>
        <field name="edt11">
          <value>Some text</value>
        </field>
      </fields>
    </xfdf>