65.9K
CodeProject 正在变化。 阅读更多。
Home

C++ 的 Linq-To-XML 风格节点创建

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.78/5 (14投票s)

2016年4月12日

Ms-PL

11分钟阅读

viewsIcon

47253

downloadIcon

530

原生 C++ 的 Linq-To-XML 节点创建

引言

本文讨论了新的 C++ Elmax XML 库功能,该功能使用 Linq-To-XML 节点创建来编写 XML 文件。目前,没有计划为 C# Elmax 实现此功能。C# 用户可以使用 .NET Linq-To-XML 来实现相同的 XML 编写。对于那些可能想了解更多关于 Elmax XML 库的读者,他们可以阅读这篇教程文章文档,但他们不必阅读这些来理解本文。本文的预期读者是 XML 库作者,他们可能对为他们的 XML 库实现此 Linq-To-XML 节点创建功能感兴趣。虽然 Linq-To-XML 节点创建已经多次被提及,但主要在原生 C++ 中工作的 C++ 程序员可能不熟悉 Linq-To-XML 节点创建的语法,以及它是什么以及它是如何工作的。简单地说,Linq-To-XML 节点创建就是用代码创建节点的一种自然方式,其结构与生成的 XML 相同。为了证明我的观点,我将展示一个 .NET C# Linq-To-XML 节点创建代码片段,用于向 movies 元素添加电影信息。

using System.Xml.Linq;

XElement movies = new XElement("Movies");

movies.Add(
    new XElement("Movie",
        new XAttribute("Name", "Transformers: Dark of the Moon"),
        new XAttribute("Year", "2011"),
        new XAttribute("RunningTime", 157.ToString()),
        new XElement("Director", "Michael Bay"),
        new XElement("Stars",
            new XElement("Actor", "Shia LaBeouf"),
            new XElement("Actress", "Rosie Huntington-Whiteley")
        ),
        new XElement("DVD",
            new XElement("Price", "25.00"),
            new XElement("Discount", (0.1).ToString())
        ),
        new XElement("BluRay",
            new XElement("Price", "36.00"),
            new XElement("Discount", (0.1).ToString())
        )
    )
);

XDocument doc = new XDocument(
    new XDeclaration("1.0", "utf-8", ""),
    movies);

doc.Save(@"C:\Temp\Movies1.xml");

供读者参考,当您按 Enter 键时,Visual Studio IDE 会自动为您缩进 C# Linq-To-XML 节点创建代码。Movies1.xml 输出看起来与下方显示的内容相似。

<?xml version="1.0" encoding="utf-8"?>
<Movies>
    <Movie Name="Transformers: Dark of the Moon" Year="2011" RunningTime="157">
        <Director>Michael Bay</Director>
        <Stars>
            <Actor>Shia LaBeouf</Actor>
            <Actress>Rosie Huntington-Whiteley</Actress>
        </Stars>
        <DVD>
            <Price>25.00</Price>
            <Discount>0.1</Discount>
        </DVD>
        <BluRay>
            <Price>36.00</Price>
            <Discount>0.1</Discount>
        </BluRay>
    </Movie>
</Movies>

从 C# 代码中很容易想象出 XML 的样子。在下一节中,我们将比较新的 Linq-To-XML 和原始的 Elmax 节点创建。

新的 Linq-To-XML 和旧的 Elmax 节点创建的比较

我想现在读者们一定很想看到 C++ 的 Linq-To-XML 语法。废话不多说,代码如下。

using namespace Elmax;

NewElement movies(L"Movies");

movies.Add(
    NewElement(L"Movie",
        NewAttribute(L"Name", L"Transformers: Dark of the Moon"),
        NewAttribute(L"Year", L"2011"),
        NewAttribute(L"RunningTime", ToStr(157)),
        NewElement(L"Director", L"Michael Bay"),
        NewElement(L"Stars",
            NewElement(L"Actor", L"Shia LaBeouf"),
            NewElement(L"Actress", L"Rosie Huntington-Whiteley")
        ),
        NewElement(L"DVD", 
            NewElement(L"Price", L"25.00"),
            NewElement(L"Discount", ToStr(0.1)) 
        ),
        NewElement(L"BluRay", 
            NewElement(L"Price", L"36.00"),
            NewElement(L"Discount", ToStr(0.1)) 
        ) 
    ) 
);

movies.Save(L"C:\\Temp\\Movies2.xml", L"1.0", true);

正如读者可能注意到的,C++ 语法不像 C# 版本那样使用 new 关键字在堆上分配元素;换句话说,元素是在栈上分配的。C# Linq-To-XML 在堆上分配元素,需要由垃圾回收器进行垃圾回收,这会影响性能并消耗更多内存。对于在栈上分配的元素,我们没有如此大的内存消耗问题,因为当元素超出作用域时,它们会立即从栈中弹出。

在表面之下,内存仍然分配在堆上以构建内部树结构。然后,在 Save 方法中,内部树结构会被递归地转换为 MS XML DOM 元素。就在 Save 方法返回之前,内部树结构会被销毁。如果用户想保留树结构以进行另一次 Save 调用或将树结构追加到更大的树结构中,他们可能不希望在 Save 期间销毁树结构;他们可以在 Save 方法中为 discard 参数(默认值为 true)指定 false(默认值为 true)。

bool Save( 
    const std::wstring& file, 
    const std::wstring& xmlVersion, 
    bool utf8, 
    bool discard = true);

bool PrettySave( 
    const std::wstring& file, 
    const std::wstring& xmlVersion, 
    bool utf8, 
    const std::wstring& indent = L"    ", 
    bool discard = true);

现在,读者可能想知道原始的 Elmax 节点创建与新的 Linq-To-XML 节点创建语法相比如何。下面的示例展示了如何使用原始的 Elmax 代码保存相同的 Movies2.xml

MSXML2::IXMLDOMDocumentPtr pDoc;
HRESULT hr = CreateAndInitDom(pDoc);
if (SUCCEEDED(hr))
{
    using namespace Elmax;
    Element root;
    root.SetConverter(NORMAL_CONV);
    root.SetDomDoc(pDoc);

    Element movies = root[L"Movies"];
    Element movie = movies[L"Movie"].CreateNew();
    movie.Attribute(L"Name") = L"Transformers: Dark of the Moon";
    movie.Attribute(L"Year") = L"2011";
    movie.Attribute(L"RunningTime") = 157;
    movie[L"Director"] = L"Michael Bay";
    movie[L"Stars|Actor"] = L"Shia LaBeouf";
    movie[L"Stars|Actress"] = L"Rosie Huntington-Whiteley";
    movie[L"DVD|Price"] = L"25.00";
    movie[L"DVD|Discount"] = 0.1;
    movie[L"BluRay|Price"] = L"36.00";
    movie[L"BluRay|Discount"] = 0.1;

    SaveXml(L"C:\\Temp\\Movies3.xml", L"1.0", true);
}

正如读者所见,仅凭 casual glances 于原始 Elmax 节点创建代码,很难辨别 XML 的结构。

库是如何编写的

令人惊讶的是,Linq-To-XML 节点创建库代码非常简单,可以在几个小时内编写完成。要创建节点,使用新语法,我们需要使用 NewElementNewAttributeNewCDataNewComment 类。这些新类都派生自 NewNode 类,它们在其构造函数中完成了大部分有用的工作。

这是 NewElement 类的声明的代码列表。

class NewElement : public NewNode
{
public:
    // Destructor
    ~NewElement(void);

    NewElement operator[](LPCWSTR name);
    NewElement operator[](LPCSTR name);

    bool Exists() { return GetPtr()!=NULL; }

    //! Copy constructor
    NewElement(const NewElement& other);
    //! Assignment operator
    NewElement& operator=(const NewElement& other);

    // Constructors
    NewElement();
    NewElement(const std::wstring& name);
    NewElement(const std::wstring& name, 
        const std::wstring& sValue);
    NewElement(const std::wstring& name, NewNode& node1);
    NewElement(const std::wstring& name, NewNode& node1, 
        NewNode& node2);
    NewElement(const std::wstring& name, NewNode& node1, 
        NewNode& node2, NewNode& node3);
    NewElement(const std::wstring& name, NewNode& node1, 
        NewNode& node2, NewNode& node3, 
        NewNode& node4);
    NewElement(const std::wstring& name, NewNode& node1, 
        NewNode& node2, NewNode& node3, 
        NewNode& node4, NewNode& node5);
    NewElement(const std::wstring& name, NewNode& node1, 
        NewNode& node2, NewNode& node3, 
        NewNode& node4, NewNode& node5, 
        NewNode& node6);
    NewElement(const std::wstring& name, NewNode& node1, 
        NewNode& node2, NewNode& node3, 
        NewNode& node4, NewNode& node5, 
        NewNode& node6, NewNode& node7);
    NewElement(const std::wstring& name, NewNode& node1, 
        NewNode& node2, NewNode& node3, 
        NewNode& node4, NewNode& node5, 
        NewNode& node6, NewNode& node7, 
        NewNode& node8);

    // ... other overloaded constructors up to 16 NewNode parameters
    // are not shown for simplicity

    NewElement Add(NewNode& node1);
    NewElement Add(NewNode& node1, NewNode& node2);
    NewElement Add(NewNode& node1, NewNode& node2, 
        NewNode& node3);
    NewElement Add(NewNode& node1, NewNode& node2, 
        NewNode& node3, NewNode& node4);
    NewElement Add(NewNode& node1, NewNode& node2, 
        NewNode& node3, NewNode& node4, 
        NewNode& node5);
    NewElement Add(NewNode& node1, NewNode& node2, 
        NewNode& node3, NewNode& node4, 
        NewNode& node5, NewNode& node6);
    NewElement Add(NewNode& node1, NewNode& node2, 
        NewNode& node3, NewNode& node4, 
        NewNode& node5, NewNode& node6, 
        NewNode& node7);
    NewElement Add(NewNode& node1, NewNode& node2, 
        NewNode& node3, NewNode& node4, 
        NewNode& node5, NewNode& node6, 
        NewNode& node7, NewNode& node8);
    
    // ... other overloaded Add methods up to 16 NewNode parameters
    // are not shown for simplicity

    bool Save(MSXML2::IXMLDOMDocumentPtr& ptrDoc, 
        const std::wstring& file, bool discard = true);
    bool PrettySave(MSXML2::IXMLDOMDocumentPtr& ptrDoc, 
        const std::wstring& file, bool discard = true);
    bool Append(NewTreeNode* child);

private:

    NewElement Find(const std::wstring& names);
    NewElement FindFirstChild(const std::wstring& name);
};

这里列出了接受 8 个 NewNode 参数的重载构造函数的代码列表。

NewElement::NewElement(const std::wstring& name, 
    NewNode& node1, NewNode& node2, 
    NewNode& node3, NewNode& node4, 
    NewNode& node5, NewNode& node6, 
    NewNode& node7, NewNode& node8)
{
    Init();
    NewTreeNode* ptr = GetPtr();
    if(ptr)
    {
        ptr->xmltype = XML_ELEMENT;
        ptr->pName = name;

        NewTreeNode* tmpPtr = node1.GetPtr();
        if(tmpPtr!=NULL)
            Append(tmpPtr);
        tmpPtr = node2.GetPtr();
        if(tmpPtr!=NULL)
            Append(tmpPtr);
        tmpPtr = node3.GetPtr();
        if(tmpPtr!=NULL)
            Append(tmpPtr);
        tmpPtr = node4.GetPtr();
        if(tmpPtr!=NULL)
            Append(tmpPtr);
        tmpPtr = node5.GetPtr();
        if(tmpPtr!=NULL)
            Append(tmpPtr);
        tmpPtr = node6.GetPtr();
        if(tmpPtr!=NULL)
            Append(tmpPtr);
        tmpPtr = node7.GetPtr();
        if(tmpPtr!=NULL)
            Append(tmpPtr);
        tmpPtr = node8.GetPtr();
        if(tmpPtr!=NULL)
            Append(tmpPtr);
    }
}

这里列出了接受 8 个 NewNode 参数的重载 Add 方法的代码列表。

NewElement NewElement::Add(
    NewNode& node1, NewNode& node2, 
    NewNode& node3, NewNode& node4, 
    NewNode& node5, NewNode& node6, 
    NewNode& node7, NewNode& node8)
{
    NewTreeNode* ptr = GetPtr();
    if(ptr)
    {
        NewTreeNode* tmpPtr = node1.GetPtr();
        if(tmpPtr!=NULL)
            Append(tmpPtr);
        tmpPtr = node2.GetPtr();
        if(tmpPtr!=NULL)
            Append(tmpPtr);
        tmpPtr = node3.GetPtr();
        if(tmpPtr!=NULL)
            Append(tmpPtr);
        tmpPtr = node4.GetPtr();
        if(tmpPtr!=NULL)
            Append(tmpPtr);
        tmpPtr = node5.GetPtr();
        if(tmpPtr!=NULL)
            Append(tmpPtr);
        tmpPtr = node6.GetPtr();
        if(tmpPtr!=NULL)
            Append(tmpPtr);
        tmpPtr = node7.GetPtr();
        if(tmpPtr!=NULL)
            Append(tmpPtr);
        tmpPtr = node8.GetPtr();
        if(tmpPtr!=NULL)
            Append(tmpPtr);
    }
    return *this;
}

正如您所见,NewElement 构造函数及其 Add 方法除了将节点附加到 vector 外,什么也不做。下面是 NewAttribute 类的声明代码列表及其唯一构造函数的定义。

class NewAttribute : public NewNode
{
public:
    // Constructor
    NewAttribute(const std::wstring& name, 
        const std::wstring& sValue);
    // Destructor
    ~NewAttribute(void);
};

NewAttribute::NewAttribute(const std::wstring& name, 
    const std::wstring& sValue)
{
    Init();
    NewTreeNode* ptr = GetPtr();
    if(ptr)
    {
        ptr->xmltype = XML_ATTRIBUTE;
        ptr->pName = name;
        ptr->pValue = sValue;
    }
}

这是 NewCData 类的声明代码列表及其唯一方法:其构造函数的定义。

class NewCData : public NewNode
{
public:
    // Constructor
    NewCData(const std::wstring& sValue);
    // Destructor
    ~NewCData(void);
};

NewCData::NewCData(const std::wstring& sValue)
{
    Init();
    NewTreeNode* ptr = GetPtr();
    if(ptr)
    {
        ptr->xmltype = XML_CDATA;
        ptr->pValue = sValue;
    }
}

这是 NewComment 类的声明代码列表及其构造函数的定义。

class NewComment : public NewNode
{
public:
    // Constructor
    NewComment(const std::wstring& sValue);
    // Destructor
    ~NewComment(void);
};

NewComment::NewComment(const std::wstring& sValue)
{
    Init();
    NewTreeNode* ptr = GetPtr();
    if(ptr)
    {
        ptr->xmltype = XML_COMMENT;
        ptr->pValue = sValue;
    }
}

读者可能会问作者为什么他选择创建新类来做这件事,而不是修改旧类,如 ElementAttributeCDataComment。原因是这些原始类包含许多数据成员;在栈上过度构建这些类并将它们从栈中弹出,会严重影响性能。从上面新类的列表可以看出,我没有列出它们的数据成员。这是因为它们唯一的共同数据成员是存在于它们基类 NewNode 中的 ptr

class NewNode
{
public:
    NewNode(void);
    ~NewNode(void);

    NewTreeNode* GetPtr() const {return ptr;}
    void SetPtr(NewTreeNode* src) { ptr = src; }

    void Init();

    void Discard();
private:
    NewTreeNode* ptr;
};

ptr 的类型是 NewTreeNode。我原本打算将这个树结构命名为 TreeNode,但 TreeNode 是 Visual C++ 10 的保留关键字,因为 Visual C++ 库中定义了另一个 TreeNode 类。

enum XMLTYPE
{
    XML_NONE,
    XML_ELEMENT,
    XML_ATTRIBUTE,
    XML_COMMENT,
    XML_CDATA
};

class NewTreeNode
{
public:
    NewTreeNode(void);
    ~NewTreeNode(void);

    std::vector<NewTreeNode*> vec;

    std::wstring pName;
    std::wstring pValue;

    XMLTYPE xmltype;

    static bool Traverse(MSXML2::IXMLDOMDocumentPtr& ptrDoc, 
        MSXML2::IXMLDOMNodePtr& parent, NewTreeNode* pNode);
    void Delete();
};

NewTreeNode 有一个 Traverse 方法,它在递归遍历树时创建 MS XML DOM 元素,还有一个 Delete 方法,它递归地删除树结构。您可以看到,在栈上分配和解分配 NewNode/NewElement 对象仅仅是推送和弹出 64 位/32 位指针的问题。与推送和弹出包含大量数据成员的重型 Element 类进行对比。供读者参考,尽管 NewNode 对象超出作用域时 64 位/32 位指针会被弹出,但指针指向的树数据仍然存在,直到它们被保存到磁盘文件。

class Element
{
private:
    //! type converter pointer
    BaseConverter* m_pIConverter;
    //! for returning wide raw array
    std::wstring m_strTemp;
    //! for returning narrow raw array
    std::string m_asciiStrTemp;
    //! Delimited string of non existing parent
    std::wstring m_strNonExistingParent;
    //! MS XML document object
    MSXML2::IXMLDOMDocumentPtr m_ptrDoc;
    //! MS XML node object
    MSXML2::IXMLDOMNodePtr m_ptrNode;
    //! Stores the deleted state
    bool m_bDeleted;
    //! Node name
    std::wstring m_strName;
    //! Stores the valid state
    bool m_bValid;
    //! State this node is root 
    //! (is true if node 1st set with SetDocDom()
    bool m_bRoot;
};

TraverseDelete 递归方法的源代码列表提供给读者参考。

bool NewElement::Traverse(NewTreeNode& node, CUnicodeFile& uf, bool utf8)
{
    if(node.xmltype==XML_ELEMENT)
    {
        WriteStartElement(uf, utf8, node.pName);

        bool attrWritten = false;
        for(size_t i=0;i<node.vec.size(); ++i)
        {
            NewTreeNode* node1 = node.vec[i];
            if(node1->xmltype==XML_ATTRIBUTE)
            {
                std::wstring str = L" ";
                str += node1->pName + L"=\"";
                str += EscapeXML(node1->pValue);
                str += L"\"";
                Write(uf, utf8, str);

                continue;
            }
            else
            {
                if(attrWritten == false)
                {
                    Write(uf, utf8, L">");
                    attrWritten = true;
                }
            }
            Traverse(*node1, uf, utf8);
        }

        if(node.vec.size()==0)
            Write(uf, utf8, L">");

        if(node.pValue.empty()==false)
        {
            std::wstring str = EscapeXML(node.pValue);
            Write(uf, utf8, str);
        }

        WriteEndElement(uf, utf8, node.pName);
    }
    else if(node.xmltype==XML_COMMENT)
    {
        std::wstring str = L"<!--";
        str += node.pValue;
        str += L"-->";
        Write(uf, utf8, str);
    }
    else if(node.xmltype==XML_CDATA)
    {
        std::wstring str = L"<![CDATA[";
        str += node.pValue;
        str += L"]]>";
        Write(uf, utf8, str);
    }

    return true;
}
void NewTreeNode::Delete()
{
    for(size_t i=0;i<vec.size();++i)
        vec.at(i)->Delete();

    vec.clear();
    delete this;
}

Linq-To-XML 查询怎么样?

虽然 Elmax 不支持 Linq-To-XML 风格的查询,但它有一些强大的查询机制,基于 Lambda(匿名函数)来决定要获取哪些元素。让我向您介绍一些 Elmax 的查询机制。

Elmax 具有 AsCollectionGetCollection 方法,它们分别获取同名兄弟节点的集合和同名子节点的集合。它们都有一个重载版本,该版本接受一个附加的 Lambda 作为谓词来过滤您想要的元素。

//! type of element vector
typedef std::vector< Element > collection_t;

//! Get the collection of sibling elements with the same name
collection_t AsCollection();

//! Get the collection of sibling elements with the same name 
//! which satisfy the boolean predicate
template<typename Predicate>
collection_t AsCollection(Predicate pred);

//! Get the collection of child elements with same name
collection_t GetCollection(const std::wstring& name);

//! Get the collection of child elements with same name, 
//! which satisfy the boolean predicate
template<typename Predicate>
collection_t GetCollection(const std::wstring& name, 
    Predicate pred);

Elmax 提供了 HyperElement 类,它允许将元素与满足特定条件的另一个元素连接起来。例如,在 Books 应用程序中,主 Books 部分下的 Book 元素将通过 AuthorID 与主 Authors 部分下的 Author 元素连接起来,以检索书籍的作者姓名。Books 部分和 Authors 部分是两个独立的部分。下面提供了一个 XML 示例。

<?xml version="1.0" encoding="UTF-16"?>
<All>
    <Version>1</Version>
    <Books>
        <Book ISBN="1111-1111-1111">
            <Title>2001: A Space Odyssey</Title>
            <Price>12.990000</Price>
            <AuthorID>111</AuthorID>
        </Book>
        <Book ISBN="2222-2222-2222">
            <Title>Rendezvous with Rama</Title>
            <Price>15.000000</Price>
            <AuthorID>111</AuthorID>
        </Book>
        <Book ISBN="3333-3333-3333">
            <Title>Foundation</Title>
            <Price>10.000000</Price>
            <AuthorID>222</AuthorID>
        </Book>
        <Book ISBN="4444-4444-4444">
            <Title>Currents of Space</Title>
            <Price>11.900000</Price>
            <AuthorID>222</AuthorID>
        </Book>
        <Book ISBN="5555-5555-5555">
            <Title>Pebbles in the Sky</Title>
            <Price>14.000000</Price>
            <AuthorID>222</AuthorID>
        </Book>
    </Books>
    <Authors>
        <Author Name="Arthur C. Clark" AuthorID="111">
            <Bio>Sci-Fic author!</Bio>
        </Author>
        <Author Name="Isaac Asimov" AuthorID="222">
            <Bio>Sci-Fic author!</Bio>
        </Author>
    </Authors>
</All>

这是 HyperElement 类与 Lambda 结合使用的示例!

auto vec = HyperElement::JoinOneToMany(
    authors.GetCollection(L"Author"), books.GetCollection(L"Book"),
    [](Elmax::Element x, Elmax::Element y)->bool 
{ 
    if(x.Attribute("AuthorID").GetString("a") == 
        y[L"AuthorID"].GetString("b") )
    {
        return true;
    }
    return false;
});

for(size_t i=0; i< vec.size(); ++i)
{
    dp.Print(L"List of books by {0}\n", 
        vec[i].first.Attribute(L"Name").GetString(""));
    dp.Print(L"=======================================\n");
    for(size_t j=0; j< vec[i].second.size(); ++j)
    {
        dp.Print(L"{0}\n", 
            vec[i].second[j][L"Title"].GetString("None"));
    }
    dp.Print(L"\n");
}

这是输出。有关 HyperElement 的更多信息,请参考Elmax 文档

List of books by Arthur C. Clark
=============================================
2001: A Space Odyssey
Rendezvous with Rama

List of books by Isaac Asimov
=============================================
Foundation
Currents of Space
Pebbles in the Sky

除了这两种查询方法,Elmax 还通过其各种 SelectNode 方法支持 XPath 表达式。

添加超过 16 个节点

NewElement 的重载构造函数和 Add 方法接受从 1 个 NewNode 对象到最多 16 个 NewNode 对象。如果用户需要为每个元素添加超过 16 个节点(例如 17 个)怎么办?答案:他/她可以使用 Add 方法,因为 Add 方法会返回自身(*this)。我将向您展示一个不使用 for-loop 为元素添加 32 个子元素的示例。实际上,对于添加超过 16 个元素的场景,for-loop 是首选方法。

NewElement hollywood(L"Hollywood");

hollywood.Add(
    NewElement(L"Stars",
        NewElement(L"Actor", L"Johnny Depp"),
        NewElement(L"Actor", L"Brad Pitt"),
        NewElement(L"Actor", L"Leonardo DiCaprio"),
        NewElement(L"Actor", L"Will Smith"),
        NewElement(L"Actor", L"George Clooney"),
        NewElement(L"Actor", L"Tom Cruise"),
        NewElement(L"Actor", L"Matt Damon"),
        NewElement(L"Actor", L"Orlando Bloom"),
        NewElement(L"Actor", L"Bruce Willis"),
        NewElement(L"Actor", L"Steve Carell"),
        NewElement(L"Actress", L"Jennifer Aniston"),
        NewElement(L"Actress", L"Jessica Alba"),
        NewElement(L"Actress", L"Halle Berry"),
        NewElement(L"Actress", L"Angelina Jolie"),
        NewElement(L"Actress", L"Sandra Bullock"),
        NewElement(L"Actress", L"Reese Witherspoon")
    ).Add(
        NewElement(L"Actress", L"Jennifer Garner"),
        NewElement(L"Actress", L"Julia Roberts"),
        NewElement(L"Actress", L"Gwyneth Paltrow"),
        NewElement(L"Actress", L"Meg Ryan"),
        NewElement(L"Actress", L"Hillary Swank"),
        NewElement(L"Actress", L"Uma Thurman"),
        NewElement(L"Actress", L"Keira Knightley"),
        NewElement(L"Actress", L"Meryl Streep"),
        NewElement(L"Actress", L"Cameron Diaz"),
        NewElement(L"Actress", L"Salma Hayek"),
        NewElement(L"Actress", L"Penelope Cruz"),
        NewElement(L"Actress", L"Nicole Kidman"),
        NewElement(L"Actress", L"Michelle Pfeiffer"),
        NewElement(L"Actress", L"Drew Barrymore"),
        NewElement(L"Actress", L"Jennifer Lopez"),
        NewElement(L"Actress", L"Catherine Zeta-Jones")
    )
);

hollywood.Save(L"C:\\Temp\\Stars.xml", L"1.0", true);

还有一种添加超过 16 个元素的方法;有一个接受 lambda 的重载 Add 方法。此方法仅在 Visual C++ 11 中可用。在早期版本的 Visual C++(例如 Visual C++ 10)中,由于 Visual C++ 10 中的 lambda 支持存在部分错误,该方法通过 _MSC_VER 检查被禁用。下面是 Add 方法的定义。

NewElement Add(auto func(NewElement& parent)->void)
{
    func(*this);
    return *this;
}

下面是一个如何使用 lambda 添加超过 16 个元素的示例。注意:parent 参数实际上指的是 hollywood 元素。

using namespace Elmax;
NewElement hollywood(L"Hollywood");

hollywood.Add([](Elmax::NewElement &parent)->void {

    using namespace Elmax;
    NewElement elem = NewElement(L"Stars");

    elem.Add(NewElement(L"Actor", L"Johnny Depp"));
    elem.Add(NewElement(L"Actor", L"Brad Pitt"));
    elem.Add(NewElement(L"Actor", L"Leonardo DiCaprio"));
    elem.Add(NewElement(L"Actor", L"Will Smith"));
    elem.Add(NewElement(L"Actor", L"George Clooney"));
    elem.Add(NewElement(L"Actor", L"Tom Cruise"));
    elem.Add(NewElement(L"Actor", L"Matt Damon"));
    elem.Add(NewElement(L"Actor", L"Orlando Bloom"));
    elem.Add(NewElement(L"Actor", L"Bruce Willis"));
    elem.Add(NewElement(L"Actor", L"Steve Carell"));
    elem.Add(NewElement(L"Actress", L"Jennifer Aniston"));
    elem.Add(NewElement(L"Actress", L"Jessica Alba"));
    elem.Add(NewElement(L"Actress", L"Halle Berry"));
    elem.Add(NewElement(L"Actress", L"Angelina Jolie"));
    elem.Add(NewElement(L"Actress", L"Sandra Bullock"));
    elem.Add(NewElement(L"Actress", L"Reese Witherspoon"));
    elem.Add(NewElement(L"Actress", L"Jennifer Garner"));
    elem.Add(NewElement(L"Actress", L"Julia Roberts"));
    elem.Add(NewElement(L"Actress", L"Gwyneth Paltrow"));
    elem.Add(NewElement(L"Actress", L"Meg Ryan"));
    elem.Add(NewElement(L"Actress", L"Hillary Swank"));
    elem.Add(NewElement(L"Actress", L"Uma Thurman"));
    elem.Add(NewElement(L"Actress", L"Keira Knightley"));
    elem.Add(NewElement(L"Actress", L"Meryl Streep"));
    elem.Add(NewElement(L"Actress", L"Cameron Diaz"));
    elem.Add(NewElement(L"Actress", L"Salma Hayek"));
    elem.Add(NewElement(L"Actress", L"Penelope Cruz"));
    elem.Add(NewElement(L"Actress", L"Nicole Kidman"));
    elem.Add(NewElement(L"Actress", L"Michelle Pfeiffer"));
    elem.Add(NewElement(L"Actress", L"Drew Barrymore"));
    elem.Add(NewElement(L"Actress", L"Jennifer Lopez"));
    elem.Add(NewElement(L"Actress", L"Catherine Zeta-Jones"));

    parent.Add(elem);
});

hollywood.Save(L"C:\\Temp\\Stars.xml", L"1.0", true);

这是保存后 Stars.xml 的样子。

<?xml version="1.0" encoding="UTF-8"?>
<Hollywood>
    <Stars>
        <Actor>Johnny Depp</Actor>
        <Actor>Brad Pitt</Actor>
        <Actor>Leonardo DiCaprio</Actor>
        <Actor>Will Smith</Actor>
        <Actor>George Clooney</Actor>
        <Actor>Tom Cruise</Actor>
        <Actor>Matt Damon</Actor>
        <Actor>Orlando Bloom</Actor>
        <Actor>Bruce Willis</Actor>
        <Actor>Steve Carell</Actor>
        <Actress>Jennifer Aniston</Actress>
        <Actress>Jessica Alba</Actress>
        <Actress>Halle Berry</Actress>
        <Actress>Angelina Jolie</Actress>
        <Actress>Sandra Bullock</Actress>
        <Actress>Reese Witherspoon</Actress>
        <Actress>Jennifer Garner</Actress>
        <Actress>Julia Roberts</Actress>
        <Actress>Gwyneth Paltrow</Actress>
        <Actress>Meg Ryan</Actress>
        <Actress>Hillary Swank</Actress>
        <Actress>Uma Thurman</Actress>
        <Actress>Keira Knightley</Actress>
        <Actress>Meryl Streep</Actress>
        <Actress>Cameron Diaz</Actress>
        <Actress>Salma Hayek</Actress>
        <Actress>Penelope Cruz</Actress>
        <Actress>Nicole Kidman</Actress>
        <Actress>Michelle Pfeiffer</Actress>
        <Actress>Drew Barrymore</Actress>
        <Actress>Jennifer Lopez</Actress>
        <Actress>Catherine Zeta-Jones</Actress>
    </Stars>
</Hollywood>

内存泄漏预防

如果您构建了一个 NewElement 对象及其子对象而没有保存,您将面临内存泄漏。因为 Save 方法在保存后会删除内部树结构,所以如果用户出于某种原因决定不保存,需要调用 Discard 方法来删除内部树结构。用户需要在此处小心,以避免内存泄漏。为了性能和内存原因,我选择不使用智能指针来存储树结构。我不喜欢在代码中使用智能指针。

兴趣点 (SAX 和 ORM)

我目前正在编写 Elmax 的 SAX 版本,以及一篇题为“程序员应该(不)阅读的 XML SAX 文章”的文章,作为对原始Elmax DOM 文章“应该(不)写的 XML 解析文章”的续篇。对于不熟悉 SAX XML 的读者;SAX 是 Simple API for XML 的缩写。SAX 在从文件读取时一次只读取一个节点。写入文件时,SAX 一次写入一个节点。SAX 不像 XML DOM 那样将 XML 节点存储在树结构中,因此 SAX 读取类似文件的内存需求与 DOM 相比非常小。SAX 版本的 Reader 和 Writer 类尽可能与 Elmax DOM 版本保持相似。对于 SAX writer 类,Linq-To-XML 节点创建语法是相似的,除了一个额外的要求。

  • 对于不在其构造函数或 Add 方法的作用域中创建的元素,需要对其调用 WriteEndElement。对于每个 XML 元素,总有一个开始元素占位符(例如 <Book>)和一个结束元素占位符(例如 </Book>),除非它没有值(例如 <Book />)。之所以提出这个要求,是因为 SAX 库无法知道用户何时停止添加子元素并希望关闭它。

这就是带有 WriteEndElement 调用的 SAX 版本电影代码的样子。

using namespace Elmax::Writer; // new namespace

NewElement movies(L"Movies");

movies.Add(
    NewElement(L"Movie",
        NewAttribute(L"Name", L"Transformers: Dark of the Moon"),
        NewAttribute(L"Year", L"2011"),
        NewAttribute(L"RunningTime", ToStr(157)),
        NewElement(L"Director", L"Michael Bay"),
        NewElement(L"Stars",
            NewElement(L"Actor", L"Shia LaBeouf"),
            NewElement(L"Actress", L"Rosie Huntington-Whiteley")
        ),
        NewElement(L"DVD", 
            NewElement(L"Price", L"25.00"),
            NewElement(L"Discount", ToStr(0.1)) 
        ),
        NewElement(L"BluRay", 
            NewElement(L"Price", L"36.00"),
            NewElement(L"Discount", ToStr(0.1)) 
        ) 
    ) 
);

// after adding all the movie
movies.WriteEndElement(); // write </Movies>
movies.Save(L"C:\\Temp\\Movies4.xml", L"1.0", true);

那么,为什么要保持 DOM 和 SAX 语法相似呢?原因有两个。首先,用户无需学习新语法或全新的库即可使用 SAX:学习曲线更低。第二个原因是,我正在使用 Elmax 编写一个 XML 对象关系映射(ORM)库,当保持两种语法相似时,DOM 和 SAX Elmax 的 ORM 代码生成器也会相似(节省了我一些编码工作)。

不常见的陷阱

我不知道是不是只有我这样:当我使用 Linq-To-XML 节点创建时,我好几次犯了错误,给我的元素和属性使用了带有空格的名称。根据 XML 规范,带有空格的名称是不允许的。我很少在使用其他传统 XML 创建方式时犯这种错误。这可能是因为 Linq-To-XML 语法将名称和值“混合”在一起:在传统的 XML 创建 API 中,我非常清楚我是在指定名称还是值。如果你们中有任何人遇到 XML 输出问题,请检查您的任何元素和属性名称是否包含空格。我指出这一点,以防任何读者分享与作者相同的智力水平。

结论

我们已经看了 .NET C# Linq-To-XML、C++ Elmax Linq-To-XML 和 C++ Elmax 原始节点创建的不同语法。我们简要讨论了 C++ Elmax Linq-To-XML 节点创建的内部工作原理。我们还研究了减少内存消耗和消除内存泄漏的方法。最后,我想给您留下添加 4 条电影信息并将其保存到 XML 的每个节点创建方法的完整代码列表。Elmax 托管在Codeplex上:您总能在那里找到最新版本。欢迎对本文提出任何建设性的反馈,无论好坏。

感谢阅读!

代码列表

.NET C# Linq-To-XML 节点创建

XElement movies = new XElement("Movies");

movies.Add(
    new XElement("Movie",
        new XAttribute("Name", "Transformers: Dark of the Moon"),
        new XAttribute("Year", "2011"),
        new XAttribute("RunningTime", 157.ToString()),
        new XElement("Director", "Michael Bay"),
        new XElement("Stars",
            new XElement("Actor", "Shia LaBeouf"),
            new XElement("Actress", "Rosie Huntington-Whiteley")
        ),
        new XElement("DVD",
            new XElement("Price", "25.00"),
            new XElement("Discount", (0.1).ToString())
        ),
        new XElement("BluRay",
            new XElement("Price", "36.00"),
            new XElement("Discount", (0.1).ToString())
        )
    )
);

movies.Add(
    new XElement("Movie",
        new XAttribute("Name", "Taken"),
        new XAttribute("Year", "2008"),
        new XAttribute("RunningTime", 93.ToString()),
        new XElement("Director", "Pierre Morel"),
        new XElement("Stars",
            new XElement("Actor", "Liam Neeson"),
            new XElement("Actress", "Maggie Grace")
        ),
        new XElement("DVD",
            new XElement("Price", "20.00"),
            new XElement("Discount", (0.2).ToString())
        ),
        new XElement("BluRay",
            new XElement("Price", "28.00"),
            new XElement("Discount", (0.2).ToString())
        )
    )
);

movies.Add(
    new XElement("Movie",
        new XAttribute("Name", "Devil"),
        new XAttribute("Year", "2010"),
        new XAttribute("RunningTime", 80.ToString()),
        new XElement("Director", "John Erick Dowdle"),
        new XElement("Stars",
            new XElement("Actor", "Chris Messina"),
            new XElement("Actor", "Bokeem Woodbine"),
            new XElement("Actress", "Caroline Dhavernas")
        ),
        new XElement("DVD",
            new XElement("Price", "19.00"),
            new XElement("Discount", (0.1).ToString())
        ),
        new XElement("BluRay",
            new XElement("Price", "26.00"),
            new XElement("Discount", (0.2).ToString())
        )
    )
);

movies.Add(
    new XElement("Movie",
        new XAttribute("Name", "Pan's Labyrinth"),
        new XAttribute("Year", "2006"),
        new XAttribute("RunningTime", 119.ToString()),
        new XElement("Director", "Guillermo del Toro"),
        new XElement("Stars",
            new XElement("Actor", "Sergi López"),
            new XElement("Actress", "Ivana Baquero"),
            new XElement("Actress", "Ariadna Gil")
        ),
        new XElement("DVD",
            new XElement("Price", "21.00"),
            new XElement("Discount", (0.2).ToString())
        ),
        new XElement("BluRay",
            new XElement("Price", "27.00"),
            new XElement("Discount", (0.2).ToString())
        )
    )
);

XDocument doc = new XDocument(
    new XDeclaration("1.0", "utf-8", ""),
    movies);
doc.Save(@"C:\Temp\Movies1.xml");

Elmax Linq-To-XML 节点创建

using namespace Elmax;

NewElement movies(L"Movies");

movies.Add(
    NewElement(L"Movie",
        NewAttribute(L"Name", L"Transformers: Dark of the Moon"),
        NewAttribute(L"Year", L"2011"),
        NewAttribute(L"RunningTime", ToStr(157)),
        NewElement(L"Director", L"Michael Bay"),
        NewElement(L"Stars",
            NewElement(L"Actor", L"Shia LaBeouf"),
            NewElement(L"Actress", L"Rosie Huntington-Whiteley")
        ),
        NewElement(L"DVD", 
            NewElement(L"Price", L"25.00"),
            NewElement(L"Discount", ToStr(0.1)) 
        ),
        NewElement(L"BluRay", 
            NewElement(L"Price", L"36.00"),
            NewElement(L"Discount", ToStr(0.1)) 
        ) 
    ) 
);

movies.Add(
    NewElement(L"Movie",
        NewAttribute(L"Name", L"Taken"),
        NewAttribute(L"Year", L"2008"),
        NewAttribute(L"RunningTime", ToStr(93)),
        NewElement(L"Director", L"Pierre Morel"),
        NewElement(L"Stars",
            NewElement(L"Actor", L"Liam Neeson"),
            NewElement(L"Actress", L"Maggie Grace")
        ),
        NewElement(L"DVD", 
            NewElement(L"Price", L"20.00"),
            NewElement(L"Discount", ToStr(0.2)) 
        ),
        NewElement(L"BluRay", 
            NewElement(L"Price", L"28.00"),
            NewElement(L"Discount", ToStr(0.2)) 
        ) 
    ) 
);

movies.Add(
    NewElement(L"Movie",
        NewAttribute(L"Name", L"Devil"),
        NewAttribute(L"Year", L"2010"),
        NewAttribute(L"RunningTime", ToStr(80)),
        NewElement(L"Director", L"John Erick Dowdle"),
        NewElement(L"Stars",
            NewElement(L"Actor", L"Chris Messina"),
            NewElement(L"Actor", L"Bokeem Woodbine"),
            NewElement(L"Actress", L"Caroline Dhavernas")
        ),
        NewElement(L"DVD", 
            NewElement(L"Price", L"19.00"),
            NewElement(L"Discount", ToStr(0.1)) 
        ),
        NewElement(L"BluRay", 
            NewElement(L"Price", L"26.00"),
            NewElement(L"Discount", ToStr(0.2)) 
        ) 
    ) 
);

movies.Add(
    NewElement(L"Movie",
        NewAttribute(L"Name", L"Pan's Labyrinth"),
        NewAttribute(L"Year", L"2006"),
        NewAttribute(L"RunningTime", ToStr(119)),
        NewElement(L"Director", L"Guillermo del Toro"),
        NewElement(L"Stars",
            NewElement(L"Actor", L"Sergi López"),
            NewElement(L"Actress", L"Ivana Baquero"),
            NewElement(L"Actress", L"Ariadna Gil")
        ),
        NewElement(L"DVD", 
            NewElement(L"Price", L"21.00"),
            NewElement(L"Discount", ToStr(0.2)) 
        ),
        NewElement(L"BluRay", 
            NewElement(L"Price", L"27.00"),
            NewElement(L"Discount", ToStr(0.2)) 
        ) 
    ) 
);

movies.Save(L"C:\\Temp\\Movies2.xml", L"1.0", true);

Elmax 节点创建

MSXML2::IXMLDOMDocumentPtr pDoc;
HRESULT hr = CreateAndInitDom(pDoc);
if (SUCCEEDED(hr))
{
    using namespace Elmax;
    Element root;
    root.SetConverter(NORMAL_CONV);
    root.SetDomDoc(pDoc);

    Element movies = root[L"Movies"];

    Element movie = movies[L"Movie"].CreateNew();
    movie.Attribute(L"Name") = L"Transformers: Dark of the Moon";
    movie.Attribute(L"Year") = L"2011";
    movie.Attribute(L"RunningTime") = 157;
    movie[L"Director"] = L"Michael Bay";
    movie[L"Stars|Actor"] = L"Shia LaBeouf";
    movie[L"Stars|Actress"] = L"Rosie Huntington-Whiteley";
    movie[L"DVD|Price"] = L"25.00";
    movie[L"DVD|Discount"] = 0.1;
    movie[L"BluRay|Price"] = L"36.00";
    movie[L"BluRay|Discount"] = 0.1;

    movie = movies[L"Movie"].CreateNew();
    movie.Attribute(L"Name") = L"Taken";
    movie.Attribute(L"Year") = L"2008";
    movie.Attribute(L"RunningTime") = 93;
    movie[L"Director"] = L"Pierre Morel";
    movie[L"Stars|Actor"] = L"Liam Neeson";
    movie[L"Stars|Actress"] = L"Maggie Grace";
    movie[L"DVD|Price"] = L"20.00";
    movie[L"DVD|Discount"] = 0.2;
    movie[L"BluRay|Price"] = L"28.00";
    movie[L"BluRay|Discount"] = 0.2;

    movie = movies[L"Movie"].CreateNew();
    movie.Attribute(L"Name") = L"Devil";
    movie.Attribute(L"Year") = L"2010";
    movie.Attribute(L"RunningTime") = 80;
    movie[L"Director"] = L"John Erick Dowdle";
    movie[L"Stars|Actor"] = L"Chris Messina";
    movie[L"Stars|Actor"].CreateNew() = L"Bokeem Woodbine";
    movie[L"Stars|Actress"] = L"Caroline Dhavernas";
    movie[L"DVD|Price"] = L"19.00";
    movie[L"DVD|Discount"] = 0.1;
    movie[L"BluRay|Price"] = L"26.00";
    movie[L"BluRay|Discount"] = 0.2;

    movie = movies[L"Movie"].CreateNew();
    movie.Attribute(L"Name") = L"Pan's Labyrinth";
    movie.Attribute(L"Year") = L"2006";
    movie.Attribute(L"RunningTime") = 119;
    movie[L"Director"] = L"Guillermo del Toro";
    movie[L"Stars|Actor"] = L"Sergi López";
    movie[L"Stars|Actress"] = L"Ivana Baquero";
    movie[L"Stars|Actress"].CreateNew() = L"Ariadna Gil";
    movie[L"DVD|Price"] = L"21.00";
    movie[L"DVD|Discount"] = 0.2;
    movie[L"BluRay|Price"] = L"27.00";
    movie[L"BluRay|Discount"] = 0.2;

    SaveXml(pDoc, L"C:\\Temp\\Movies3.xml");
}

这是 XML 输出。

<?xml version="1.0" encoding="utf-8"?>
<Movies>
    <Movie Name="Transformers: Dark of the Moon" Year="2011" RunningTime="157">
        <Director>Michael Bay</Director>
        <Stars>
            <Actor>Shia LaBeouf</Actor>
            <Actress>Rosie Huntington-Whiteley</Actress>
        </Stars>
        <DVD>
            <Price>25.00</Price>
            <Discount>0.1</Discount>
        </DVD>
        <BluRay>
            <Price>36.00</Price>
            <Discount>0.1</Discount>
        </BluRay>
    </Movie>
    <Movie Name="Taken" Year="2008" RunningTime="93">
        <Director>Pierre Morel</Director>
        <Stars>
            <Actor>Liam Neeson</Actor>
            <Actress>Maggie Grace</Actress>
        </Stars>
        <DVD>
            <Price>20.00</Price>
            <Discount>0.2</Discount>
        </DVD>
        <BluRay>
            <Price>28.00</Price>
            <Discount>0.2</Discount>
        </BluRay>
    </Movie>
    <Movie Name="Devil" Year="2010" RunningTime="80">
        <Director>John Erick Dowdle</Director>
        <Stars>
            <Actor>Chris Messina</Actor>
            <Actor>Bokeem Woodbine</Actor>
            <Actress>Caroline Dhavernas</Actress>
        </Stars>
        <DVD>
            <Price>19.00</Price>
            <Discount>0.1</Discount>
        </DVD>
        <BluRay>
            <Price>26.00</Price>
            <Discount>0.2</Discount>
        </BluRay>
    </Movie>
    <Movie Name="Pan's Labyrinth" Year="2006" RunningTime="119">
        <Director>Guillermo del Toro</Director>
        <Stars>
            <Actor>Sergi López</Actor>
            <Actress>Ivana Baquero</Actress>
            <Actress>Ariadna Gil</Actress>
        </Stars>
        <DVD>
            <Price>21.00</Price>
            <Discount>0.2</Discount>
        </DVD>
        <BluRay>
            <Price>27.00</Price>
            <Discount>0.2</Discount>
        </BluRay>
    </Movie>
</Movies>

历史

  • 2012-06-06:在“添加超过 16 个节点”部分添加了另一种添加超过 16 个元素的方法。
  • 2012-04-10:更新了源代码(至 0.84 beta 版),包含PJ Arends 的 RootElement 类和 Elmax.h 头文件。
  • 2012-04-09:更新了源代码(至 0.83 beta 版),包含PJ Arends 对 PrettySave 和 Save 方法缺失闭合标签(如果没有子元素)的修复。
  • 2011-11-04:更新了源代码(至 0.82 beta 版),不再使用 MS XML 进行保存,以降低内存需求并提高性能。修复了 PrettySave 方法,因为 MSDN 论坛的先前版本不起作用。文章中移除了内存消耗部分。
  • 2011-10-20:初始发布
© . All rights reserved.