【问题标题】:How to implement a generic DOM data structure in C++?如何在 C++ 中实现一个通用的 DOM 数据结构?
【发布时间】:2010-10-19 00:40:18
【问题描述】:

我正在尝试编写一个非常简单的文档对象模型库实现,以便提供一个通用数据结构以在我的进一步项目中使用。 为了简单起见,我只定义了三个主要类:nodeelementattribute。节点由其名称定义(例如所有 html 标记),基本上是元素的容器,可以是文本和子节点(存储在 std::vector<node> 中)。

我只是不知道如何定义整个树结构。

我需要我提到的类的模板化接口。

使用示例:

element<string> txt1("Some text");

element< element<string> > div1("div", txt1);

我不想创建一个完全支持 XML 的完整 DOM 抽象级别。我只需要一些想法来以类似 DOM 的方式组织信息。不需要解析。

提前致谢!

【问题讨论】:

  • +1,现在我更好地理解了这个问题。我以为您正在尝试实现 XML 库:)

标签: c++ templates dom


【解决方案1】:

不要尝试根据每个节点的父节点数量来强类型化,而是将代码组织为树结构:

class Element
{
public:
  std::string Name;
  std::map<std::string, std::string, std::less<std::string> > Attributes;
  std::list<Element> Children;
};

您的公共界面可能看起来与此大不相同。我只是想展示一般的类型布局。

您实际上并不需要节点或属性功能,除非您需要在集合中与元素一起迭代它们。这对于 XML DOM 库来说是一个有用的功能,但如果您只是想创建一个数据结构,则不必完全遵循 DOM 设计。

事实上,如果你只是想要一个通用的数据结构,你可能只需要一个属性包:

#include<map>
#include<string>
#include<iostream>

class PropertyBag;
typedef std::map<std::string, PropertyBag> PropertyMap;

class PropertyBag : public PropertyMap
{
public:
  PropertyBag(const std::string& value)
    : value(value)
  {
  }

  PropertyBag& operator=(const std::string& value)
  {
    this->value = value;
    return *this;
  }

  operator std::string& () { return value; }

private:
  std::string value;

  friend PropertyMap::mapped_type& PropertyMap::operator[](const PropertyMap::key_type&);
  PropertyBag() { }
};

void SomeFunction(const std::string& value)
{
  std::cout << value << "\n";
}

int main(int argc, char* argv[])
{
  PropertyBag config("configuration root");
  config["child1"] = "value1";
  config["child1"]["subchild1"] = "value2";

  SomeFunction(config["child1"]);
  SomeFunction(config["child1"]["subchild1"]);
  return 0;
}

就语法而言,您也可以尝试重载operator() 和/或链接方法来解决问题:

PropertyBag& SomeMethod(const std::string& someParam)
{
  // do something here...
  return *this;
}

PropertyBag& operator()(const std::string& p1, const std::string& p2)
{
  // ...
  return *this;
}

// ...

Configuration config1("root")
  .SomeMethod("p1")
  .SomeMethod("p2");
Configuration config2("root")
  ("Something", "blah")
  ("sizzle", "V2");

我想文本/代码重复越少越好。您可以让代码越接近 JSON 或 YAML 之类的语法越好。

一旦 c++0x 出现,您可能会有更简单的选择。您还可以查看 boost::assign library 以获得用于数据结构的简单初始化语法。

您还可以在boost::any library 中查找可以用作值的数据类型,而不是字符串(支持插入任何值的类型安全方法,只要将其提取为相同类型即可)。

【讨论】:

  • @Merlyn: list 的运行时性能很差,你确定你不想要一个向量吗?
  • @Matthieu:一个列表可以更好地代表您在这样的结构上执行的操作。您不知道开始的大小,并且您永远不会对子项进行基于索引的访问。在这种情况下,列表可以更好地用于构造,并且与遍历一样快。但在现实生活中,这种性能很少会成为瓶颈,如果是这样,您会为应用程序的该部分提出自定义存储解决方案,而不是通用数据结构。
  • @Merlyn:寻求自定义存储解决方案是最后的解决方案,仅供专家使用。至于列表选择,我强烈反对。容器的首选应该是vectordeque,因为它们更简单。仅当您有特定要求时才应该更改:unordered_set 用于唯一性,set 用于订购,stack / queue 用于特定访问等等......通常使用list 的唯一优势,是迭代器失效保证。
  • @Merlyn:这正是我想要的——组织数据(例如,通过 C++ 运算符重载功能提供的语法更改)以使其表示代码更接近 JSON/YAML/XML 逻辑。尽管我不想像您在示例源中那样直接使用它。我认为定义一个后台数据架构(例如本例的 DOM)来存储数据更方便。并且,独立地有一个接口将用户友好的代码转换为抽象形式。因为我打算主要将它用于 xHTML,所以 DOM 将是最合适的。我错了吗?
  • @Matthieu:“通常,使用列表的唯一优点是迭代器失效保证” - 不正确。如果你为一个向量 push_back 一个节点,插入需要多长时间?向量中已经存在的对象究竟会发生什么?你不知道,因为它取决于向量中已经存在的内容,以及复制实现的实现。使用列表,您可以修改列表,而不是其内容。您插入一个对象,它会执行一个或零个副本,并且(可能)更少的分配。对我来说,这听起来像是可取的行为。
【解决方案2】:

如果您查看我过去的答案,您会发现我是模板的支持者,但如果您没有其他要求,它们只会妨碍您。解析器不喜欢处理很多不同的类型。 (虽然你说你不需要解析器——嗯?)

XML 和 DOM 的重点是使其易于在任何内部结构之间进行转换。您不仅不需要定义 XML 节点模板,还不需要 any 类型的自定义数据结构。任何结构都已经是类似 DOM 的样式。 DAG 很麻烦,因为它们有点像树和有点像图,但你并没有暗示你遇到了这种障碍。

您说(在对已删除答案的评论中)您不想使用现有库。为什么?你真正想做的是什么?

【讨论】:

  • “虽然你说你不需要解析器——嗯?”是的!没错,我不需要解析器!我的解析方法是一个非常隐含的过程。我可以给你看一些例子。我正在做的是一个 html 的模板引擎——所以我想要一个抽象的 html 数据表示来在 DOM 树操作中使用。
  • @Rizo:在这种情况下,您正在编写一个解析器并且您正在创建自己的 HTML(不是 XML)库。就像我说的,除非有使用模板的理由,否则它们只会碍事。
  • 我只是认为 xml 在语法和逻辑上等同于 html(不是吗?)。
  • @Rizo:不,它们是两种不同的标准,具有不同的结构和编码。查查他们。如果没有深入阅读 HTML 标准,我有点不相信你真的在编写 HTML 引擎……编辑:我看到你对 Matthieu 的评论;要么你读过这篇文章,要么你真的需要:en.wikipedia.org/wiki/XHTML
  • 我没有说他们是平等的。只是等价。据我所知,xHTML 是 XML 的直接应用程序,可以使用 XML 解析器进行解析。我应该说“xhtml”而不是“html”。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-02-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-07-23
  • 1970-01-01
  • 2020-04-20
相关资源
最近更新 更多