XML Tutorial/zh CN

From Lazarus wiki
Revision as of 10:57, 26 July 2013 by Chronos (talk | contribs)
Jump to navigationJump to search

Deutsch (de) English (en) español (es) français (fr) magyar (hu) Bahasa Indonesia (id) italiano (it) 日本語 (ja) 한국어 (ko) português (pt) русский (ru) 中文(中国大陆)‎ (zh_CN)

可扩展标记语言(XML)是一个世界万维网组织(或W3C)推荐的在不同系统间交换信息的语言。

它是一种基于文本的储存信息的方式,而不是直接基于二进制数据的。

许多现代的电子数据交换语言,比如XHTML,包括世界上最流行的网络技术都是基于XML的。这篇WIKI只能给出一些关于XML的极短的描述,主要的关注点在如何在Pascal中使用XML文件,如果你对XML十分感兴趣,你可以访问XML的维基百科


介绍

目前,有一大堆的单元提供Pascal中的XML支持,这些单元包括 "XMLRead","XMLWrite"和"DOM",他们是FPC中的免费组件库(FCL)的一部分,FCL已经包括在Lazarus编译器的单元搜索路径中了,所以你只需要在uses中加上要使用的单元名就可以获得XML的支持。FCL的文档并不完整(至少在2005年10月),所以这篇简短的教程将将把侧重点放在如何使用这些单元上。

XML DOM(文档对象模型)是一些在不同语言与系统中提供了相似的XML支持的标准对象,这个标准只描述了方法、属性和其他一些对象的接口,把具体的实现交给了各种语言。FCL完全支持XML DOM 2.0以及它的子集XML DOM 3.0列出的这些内容.

使用范例

下面有一些从简单到复杂的XML操作示例。

在Uses中引用的单元:Unicode或Ansi

FPC带来的XML单元通常使用ANSI编码所以在任何一个平台上编码都有可能不同,可能不是Uniocde。 Lazarus带来另一个单独的XML单元且在所有平台都完全支持UTF-8。这些单元是互相兼容的而且可以通过修改Uses来进行切换。

FPC中使用系统编码的字符串德XML支持单元:

  • DOM
  • XMLRead
  • XMLWrite
  • XMLCfg
  • XMLUtils
  • XMLStreaming

Lazarus中完全使用UTF-8 Unicode的XML支持单元:

  • laz2_DOM
  • laz2_XMLRead
  • laz2_XMLWrite
  • laz2_XMLCfg
  • laz2_XMLUtils
  • laz_XMLStreaming

然而,并不是在每个实例中都需要其中所有的单元,你需要DOM单元因为它定义了很多的类型包括TXMLDocument。

读取一个XML节点

致Delphi程序员:

请注意使用TXMLDocument时,节点中的文本被囊括在独立的TEXT节点中,所以,你必须作为单独的节点访问一个节点的文本值。 另外,"TextContent"属性可以检索在一个节点中所有的文本节点并衔接在一起。

ReadXMLFile过程总是创建一个新的TXMLDocument对象,所以你不必预先创建,但是,当你使用完后请牢记使用Free过程释放XML文档。

例如,思考下面的XML文档:

<?xml version="1.0"?>
<request>
  <request_type>PUT_FILE</request_type>
  <username>123</username>
  <password>abc</password>
</request>

下面的代码同时展示了正确与错误的获取文本值得方式:(添加laz2_XMLReadlaz2_DOM 到uses列表)

var
  PassNode: TDOMNode;
  Doc: TXMLDocument;
begin
  try
    // 从磁盘读取XML文档
    ReadXMLFile(Doc, 'test.xml');
    // 获取Password节点
    PassNode := Doc.DocumentElement.FindNode('password');
    // 输出节点的值
    WriteLn(PassNode.NodeValue); // 将会输出空白
    // 节点的文本值在独立的子节点中
    WriteLn(PassNode.FirstChild.NodeValue); // 正确地输出 "abc"
    // 另一种选择
    WriteLn(PassNode.TextContent);
  finally
    // 最后,释放XML文档
    Doc.Free;
  end;
end;

请注意ReadXMLFile在解析时忽略所有空白字符,空白字符类目将会教授你如何保留它们

输出每一个节点和属性的名字

If you want to navigate the DOM tree: when you need to access nodes in sequence, it is best to use FirstChild and NextSibling properties (to iterate forward), or LastChild and PreviousSibling (to iterate backward).

For random access it is possible to use ChildNodes or GetElementsByTagName methods, but these will create a TDOMNodeList object which eventually must be freed. This differs from other DOM implementations like MSXML, because the FCL implementation is object-based, not interface-based.

The following example shows how to print the names of nodes to a TMemo placed on a form.

Below is the XML file called 'test.xml':

<?xml version="1.0"?>
<images directory="mydir">
  <imageNode URL="graphic.jpg" title="">
    <Peca DestinoX="0" DestinoY="0">Pecacastelo.jpg1.swf</Peca>
    <Peca DestinoX="0" DestinoY="86">Pecacastelo.jpg2.swf</Peca>
  </imageNode>
</images>

And here the Pascal code to execute the task:

var
  Doc: TXMLDocument;
  Child: TDOMNode;
  j: Integer;
begin
  try
    ReadXMLFile(Doc, 'test.xml');
    Memo.Lines.Clear;
    // using FirstChild and NextSibling properties
    Child := Doc.DocumentElement.FirstChild;
    while Assigned(Child) do
    begin
      Memo.Lines.Add(Child.NodeName + ' ' + Child.Attributes.Item[0].NodeValue);
      // using ChildNodes method
      with Child.ChildNodes do
      try
        for j := 0 to (Count - 1) do
          Memo.Lines.Add(format('%s %s (%s=%s; %s=%s)',
                                [
                                  Item[j].NodeName,
                                  Item[j].FirstChild.NodeValue,
                                  Item[j].Attributes.Item[0].NodeName,  // 1st attribute details
                                  Item[j].Attributes.Item[0].NodeValue,
                                  Item[j].Attributes.Item[1].NodeName,  // 2nd attribute details
                                  Item[j].Attributes.Item[1].NodeValue
                                ]));
      finally
        Free;
      end;
      Child := Child.NextSibling;
    end;
  finally
    Doc.Free;
  end;
end;

This will print:

imageNode graphic.jpg
Peca Pecacastelo.jpg1.swf (DestinoX=0; DestinoY=0)
Peca Pecacastelo.jpg2.swf (DestinoX=0; DestinoY=86)

将树状图转化为XML

One common use of XML files is to parse them and show their contents in a tree like format. You can find the TTreeView component on the "Common Controls" tab on Lazarus.

The function below will take a XML document previously loaded from a file or generated on code, and will populate a TreeView with it´s contents. The caption of each node will be the content of the first attribute of each node.

procedure TForm1.XML2Tree(tree: TTreeView; XMLDoc: TXMLDocument);
var
  iNode: TDOMNode;

  procedure ProcessNode(Node: TDOMNode; TreeNode: TTreeNode);
  var
    cNode: TDOMNode;
    s: string;
  begin
    if Node = nil then Exit; // Stops if reached a leaf
    
    // Adds a node to the tree
    if Node.HasAttributes and (Node.Attributes.Length>0) then
      s := Node.Attributes[0].NodeValue
    else
      s := ''; 
    TreeNode := tree.Items.AddChild(TreeNode, s);

    // Goes to the child node
    cNode := Node.FirstChild;

    // Processes all child nodes
    while cNode <> nil do
    begin
      ProcessNode(cNode, TreeNode);
      cNode := cNode.NextSibling;
    end;
  end;
    
begin
  iNode := XMLDoc.DocumentElement.FirstChild;
  while iNode <> nil do
  begin
    ProcessNode(iNode, nil); // Recursive
    iNode := iNode.NextSibling;
  end;
end;

Another example that displays the complete XML structure including all attribute values (note: the long line referencing TreeView has been split so it will word wrap for this wiki; when writing it in code you do not have to break the line unless you like the formatting) :

procedure XML2Tree(XMLDoc:TXMLDocument; TreeView:TTreeView);

  // Local function that outputs all node attributes as a string
  function GetNodeAttributesAsString(pNode: TDOMNode):string;
  var i: integer;
  begin
    Result:='';
    if pNode.HasAttributes then
      for i := 0 to pNode.Attributes.Length -1 do
        with pNode.Attributes[i] do
          Result := Result + format(' %s="%s"', [NodeName, NodeValue]);

    // Remove leading and trailing spaces
    Result:=Trim(Result);
  end;

  // Recursive function to process a node and all its child nodes 

  procedure ParseXML(Node:TDOMNode; TreeNode: TTreeNode);
  begin
    // Exit procedure if no more nodes to process
    if Node = nil then Exit;

    // Add node to TreeView
    TreeNode := TreeView.Items.AddChild(TreeNode, 
                                          Trim(Node.NodeName+' '+ 
                                           GetNodeAttributesAsString(Node)+ 
                                           Node.NodeValue)
                                        );

    // Process all child nodes
    Node := Node.FirstChild;
    while Node <> Nil do
    begin
      ParseXML(Node, TreeNode);
      Node := Node.NextSibling;
    end;
  end;

begin
  TreeView.Items.Clear;
  ParseXML(XMLDoc.DocumentElement,nil);
end;

修改一个XML文档

The first thing to remember is that TDOMDocument is the "handle" to the DOM. You can get an instance of this class by creating one or by loading a XML document.

Nodes on the other hand cannot be created like a normal object. You *must* use the methods provided by TDOMDocument to create them, and later use other methods to put them in the correct place in the tree. This is because nodes must be "owned" by a specific document in DOM.

Below are some common methods from TDOMDocument:

function CreateElement(const tagName: DOMString): TDOMElement; virtual;
function CreateTextNode(const data: DOMString): TDOMText;
function CreateCDATASection(const data: DOMString): TDOMCDATASection; virtual;
function CreateAttribute(const name: DOMString): TDOMAttr; virtual;

CreateElement creates a new element.

CreateTextNode creates a text node.

CreateAttribute creates an attribute node.

CreateCDATASection creates a CDATA section: regular XML markup characters such as <> are not interpreted within the CDATA section. See Wikipedia article on CDATA

A more convenient method to manipulate attributes is to use TDOMElement.SetAttribute method, which is also represented as the default property of TDOMElement:

// these two statements are equivalent
Element.SetAttribute('name', 'value');
Element['name'] := 'value';

And here an example method that will locate the selected item on a TTreeView and then insert a child node to the XML document it represents. The TreeView must be previously filled with the contents of an XML file using the XML2Tree function.

procedure TForm1.actAddChildNode(Sender: TObject);
var
  position: Integer;
  NovoNo: TDomNode;
begin
  {*******************************************************************
  *  Detects the selected element
  *******************************************************************}
  if TreeView1.Selected = nil then Exit;

  if TreeView1.Selected.Level = 0 then
  begin
    position := TreeView1.Selected.Index;

    NovoNo := XMLDoc.CreateElement('item');
    TDOMElement(NovoNo).SetAttribute('nome', 'Item');
    TDOMElement(NovoNo).SetAttribute('arquivo', 'Arquivo');
    with XMLDoc.DocumentElement.ChildNodes do
    begin
      Item[position].AppendChild(NovoNo);
      Free;
    end;

    {*******************************************************************
    *  Updates the TreeView
    *******************************************************************}
    TreeView1.Items.Clear;
    XML2Tree(TreeView1, XMLDoc);
  end
  else if TreeView1.Selected.Level >= 1 then
  begin
    {*******************************************************************
    *  This function only works on the first level of the tree,
    *  but can easily be modified to work for any number of levels
    *******************************************************************}
  end;
end;

从字符串创建TXMLDocument

Given an XML document in string variable MyXmlString, the following code will create it's DOM:

var
  S: TStringStream;
  XML: TXMLDocument;
begin
  S := TStringStream.Create(MyXMLString);
  try
    // Read complete XML document
    ReadXMLFile(XML, S);             
    // Alternatively: read only an XML Fragment
    ReadXMLFragment(AParentNode, S); 
  finally
    S.Free;
  end;
end;

验证一个XML文档

Since March 2007, DTD validation facility has been added to the FCL XML parser. Validation is checking that logical structure of the document conforms to the predefined rules, called Document Type Definition (DTD).

Here is an example of XML document with a DTD:

<?xml version='1.0'?>
<!DOCTYPE root [
<!ELEMENT root (child)+ >
<!ELEMENT child (#PCDATA)>
]>
<root>
  <child>This is a first child.</child>
  <child>And this is the second one.</child>
</root>

This DTD specifies that 'root' element must have one or more 'child' elements, and that 'child' elements may have only character data inside. If parser detects any violations from these rules, it will report them.

Loading such document is slightly more complicated. Let's assume we have XML data in a TStream object:

procedure TMyObject.DOMFromStream(AStream: TStream);
var
  Parser: TDOMParser;
  Src: TXMLInputSource;
  TheDoc: TXMLDocument;
begin
  try
    // create a parser object
    Parser := TDOMParser.Create;
    // and the input source
    Src := TXMLInputSource.Create(AStream);
    // we want validation
    Parser.Options.Validate := True;
    // assign a error handler which will receive notifications
    Parser.OnError := @ErrorHandler;
    // now do the job
    Parser.Parse(Src, TheDoc);
    // ...and cleanup
  finally
    Src.Free;
    Parser.Free;
  end;
end;

procedure TMyObject.ErrorHandler(E: EXMLReadError);
begin
  if E.Severity = esError then  // we are interested in validation errors only
    writeln(E.Message);
end;

空字符

If you want to preserve leading whitespace characters in node texts, the above method is the way to load your XML document. Leading whitespace characters are ignored by default. That is the reason why the ReadXML(...) function never returns any leading whitespace characters in node texts. Before calling Parser.Parse(Src, TheDoc) insert the line

Parser.Options.PreserveWhitespace := True;

This will force the parser to return all whitespace characters. This includes all the newline characters that exist in an XML document to make it more readable!

生成一个XML文件

Below is the complete code to write a XML file. (This was taken from a tutorial in the DeveLazarus blog) Please, remember to include the DOM and XMLWrite units in your uses clause.

unit Unit1;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, LResources, Forms, Controls, Graphics, Dialogs, StdCtrls,
  DOM, XMLWrite;

type
  { TForm1 }
  TForm1 = class(TForm)
    Button1: TButton;
    Label1: TLabel;
    Label2: TLabel;
    procedure Button1Click(Sender: TObject);
  private
    { private declarations }
  public
    { public declarations }
  end;
  
var
  Form1: TForm1;
  
implementation

{ TForm1 }

procedure TForm1.Button1Click(Sender: TObject);
var
  Doc: TXMLDocument;                                  // variable to document
  RootNode, parentNode, nofilho: TDOMNode;                    // variable to nodes
begin
  try
    // Create a document
    Doc := TXMLDocument.Create;

    // Create a root node
    RootNode := Doc.CreateElement('register');
    Doc.Appendchild(RootNode);                           // save root node
  
    // Create a parent node
    RootNode:= Doc.DocumentElement;
    parentNode := Doc.CreateElement('usuario');
    TDOMElement(parentNode).SetAttribute('id', '001');       // create atributes to parent node
    RootNode.Appendchild(parentNode);                          // save parent node

    // Create a child node
    parentNode := Doc.CreateElement('nome');                // create a child node
    // TDOMElement(parentNode).SetAttribute('sexo', 'M');     // create atributes
    nofilho := Doc.CreateTextNode('Fernando');         // insert a value to node
    parentNode.Appendchild(nofilho);                         // save node
    RootNode.ChildNodes.Item[0].AppendChild(parentNode);       // insert child node in respective parent node
 
    // Create a child node
    parentNode := Doc.CreateElement('idade');               // create a child node
    // TDOMElement(parentNode).SetAttribute('ano', '1976');   // create atributes
    nofilho := Doc.CreateTextNode('32');               // insert a value to node
    parentNode.Appendchild(nofilho);                         // save node
    RootNode.ChildNodes.Item[0].AppendChild(parentNode);       // insert a childnode in respective parent node

    writeXMLFile(Doc, 'test.xml');                     // write to XML
  finally
    Doc.Free;                                          // free memory
  end;
end;

initialization
  {$I unit1.lrs}

end.

The result will be the XML file below:

<?xml version="1.0"?>
<register>
  <usuario id="001">
    <nome>Fernando</nome>
    <idade>32</idade>
  </usuario>
</register>

An example where you don't need to reference an item by index.

procedure TForm1.Button2Click(Sender: TObject);
var
  Doc: TXMLDocument;
  RootNode, ElementNode,ItemNode,TextNode: TDOMNode;
  i: integer;
begin
  try
    // Create a document
    Doc := TXMLDocument.Create;
    // Create a root node
    RootNode := Doc.CreateElement('Root');
    Doc.Appendchild(RootNode);
    RootNode:= Doc.DocumentElement;
    // Create nodes
    for i := 1 to 20 do
    begin
      ElementNode:=Doc.CreateElement('Element');
      TDOMElement(ElementNode).SetAttribute('id', IntToStr(i));

      ItemNode:=Doc.CreateElement('Item1');
      TDOMElement(ItemNode).SetAttribute('Attr1', IntToStr(i));
      TDOMElement(ItemNode).SetAttribute('Attr2', IntToStr(i));
      TextNode:=Doc.CreateTextNode('Item1Value is '+IntToStr(i));
      ItemNode.AppendChild(TextNode);
      ElementNode.AppendChild(ItemNode);

      ItemNode:=Doc.CreateElement('Item2');
      TDOMElement(ItemNode).SetAttribute('Attr1', IntToStr(i));
      TDOMElement(ItemNode).SetAttribute('Attr2', IntToStr(i));
      TextNode:=Doc.CreateTextNode('Item2Value is '+IntToStr(i));
      ItemNode.AppendChild(TextNode);
      ElementNode.AppendChild(ItemNode);

      RootNode.AppendChild(ElementNode);
    end;
    // Save XML
    WriteXMLFile(Doc,'TestXML_v2.xml');
  finally
    Doc.Free;
  end;

Generated XML:

<?xml version="1.0"?>
<Root>
  <Element id="1">
    <Item1 Attr1="1" Attr2="1">Item1Value is 1</Item1>
    <Item2 Attr1="1" Attr2="1">Item2Value is 1</Item2>
  </Element>
  <Element id="2">
    <Item1 Attr1="2" Attr2="2">Item1Value is 2</Item1>
    <Item2 Attr1="2" Attr2="2">Item2Value is 2</Item2>
  </Element>
  <Element id="3">
    <Item1 Attr1="3" Attr2="3">Item1Value is 3</Item1>
    <Item2 Attr1="3" Attr2="3">Item2Value is 3</Item2>
  </Element>
</Root>

编码

Starting from FPC version 2.4 (more precisely, from SVN revision 12582), XML reader is able to process data in any encoding by using external decoders. See XML_Decoders for more details.

According to the XML standard, the encoding attribute in the first line of the XML is optional in case the actual encoding is UTF-8 (without BOM - Byte Order Marker) or UTF-16 (UTF-16 BOM).

TXMLDocument has an encoding property since FPC 2.4. It is ignored as WriteXMLFile always uses UTF-8.

  • FPC 2.4 doesn´t generate an encoding attribute in the first line of the XML file
  • FPC 2.6.0 and later explicitly write an UTF8 encoding attribute, as this is needed for some programs that cannot handle the XML without it.

相关链接

扩展链接