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






4.78/5 (14投票s)
原生 C++ 的 Linq-To-XML 节点创建
- 引言
- 新的 Linq-To-XML 和旧的 Elmax 节点创建的比较
- 库是如何编写的
- Linq-To-XML 查询怎么样?
- 添加超过 16 个节点
- 内存泄漏预防
- 兴趣点 (SAX 和 ORM)
- 不常见的陷阱
- 结论
- 代码列表
- 历史
引言
本文讨论了新的 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 节点创建库代码非常简单,可以在几个小时内编写完成。要创建节点,使用新语法,我们需要使用 NewElement
、NewAttribute
、NewCData
和 NewComment
类。这些新类都派生自 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;
}
}
读者可能会问作者为什么他选择创建新类来做这件事,而不是修改旧类,如 Element
、Attribute
、CData
和 Comment
。原因是这些原始类包含许多数据成员;在栈上过度构建这些类并将它们从栈中弹出,会严重影响性能。从上面新类的列表可以看出,我没有列出它们的数据成员。这是因为它们唯一的共同数据成员是存在于它们基类 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;
};
Traverse
和 Delete
递归方法的源代码列表提供给读者参考。
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 具有 AsCollection
和 GetCollection
方法,它们分别获取同名兄弟节点的集合和同名子节点的集合。它们都有一个重载版本,该版本接受一个附加的 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:初始发布