如何在嵌套的 dom 树中提取特定 dom 属性的列表?



我想遍历并收集具有data-table属性的节点,提取其值,然后获取具有data-field或其他属性的子节点,并提取其值,该值将另存为列表。

从下面的 HTML 示例中,我在dom-tree中设置了dom-attributes的锚点,旨在在遍历和提取它们后将其转换为模型结构。

<body>
<div class="wrap" data-table="page"> Sample Text <p data-field="heading" class="format" >Welcome to this page</p>
<div class="flex-grid generic-card">
<h1 class="card " data-field="intro">Text </h1>
<div class="card " data-field="body"></div>
</div>
</div>

我希望最终结果采用类似于(page . ("title" "intro" "body"))的平面列表形式

使用以下代码,我能够遍历节点并提取'data-table'但问题是,我无法提取附加到data-tabledata-field。 我尝试使用递归方法,该方法包括重复'dom-struct'dom-search函数的示例,但没有成功。 我注意到的是libxml-parse-html-region''在解析 dom 树后,会在 dom 节点旁边返回带有换行符的空字符串,这会生成错误。

此代码的目的是递归地从树中提取节点

(require 'dom)
(defun dom-struct (x)
(print (dom-attr x 'data-table)) ; extract the data-table attribute
(print (dom-tag (dom-node x)))        ;extract dom-tag
(print (dom-children (dom-node x))) ; extract dom-children attached to a node but don't know how to extract data-field attribute
(print (dom-search (dom-children (dom-node x)) (lambda (node) (assq 'data-attribute (cadr node)))))
(mapconcat #'dom-struct (dom-children (dom-node x)) ""))
(defun macro-structify (tag-entries)
(with-temp-buffer
(insert tag-entries)
(let* ((mytags (libxml-parse-html-region (point-min) (point-max))))
(dom-struct (car (dom-by-tag mytags 'body))))))
(let ((myskel "<html>
<head>
<title>Demo: Gradient Slide</title>
</head>
<link href="https://fonts.googleapis.com/css?family=Nunito+Sans" rel="stylesheet">
<link rel="stylesheet" href="dist/build.css">
<body data-table="layout">
<header data-field="title">
<h1>Skeleton Screen</h1>
</header>
<div class="wrap" data-table="page"> Sample Text <p data-field="heading" class="format" data-attribute="somethingsomething">Welcome to this page</p>
<div class="flex-grid generic-card">
<div class="card loading" data-field="intro">Text </div>
<div class="card loading" data-field="body"></div>
</div>
</div>
</body>
</html>"))
(macro-structify myskel))

下面是使用 esxml 包中的 esxml-query 的解决方案。它查找具有data-field属性的所有节点,这些节点是具有data-table属性的div节点的子节点,然后将其属性值收集到列表中。

(require 'dom)
(require 'esxml-query)
(let* ((myskel "<html>
<head>
<title>Demo: Gradient Slide</title>
</head>
<link href="https://fonts.googleapis.com/css?family=Nunito+Sans" rel="stylesheet">
<link rel="stylesheet" href="dist/build.css">
<body data-table="layout">
<header data-field="title">
<h1>Skeleton Screen</h1>
</header>
<div class="wrap" data-table="page"> Sample Text <p data-field="heading" class="format" data-attribute="somethingsomething">Welcome to this page</p>
<div class="flex-grid generic-card">
<div class="card loading" data-field="intro">Text </div>
<div class="card loading" data-field="body"></div>
</div>
</div>
</body>
</html>")
(dom (with-temp-buffer
(insert myskel)
(libxml-parse-html-region (point-min) (point-max))))
(table-node (esxml-query "div[data-table]" dom))
(model-nodes (esxml-query-all "[data-field]" table-node))
(model-data-table (dom-attr table-node 'data-table))
(model-data-fields (mapcar (lambda (node) (dom-attr node 'data-field)) model-nodes)))
(cons model-data-table model-data-fields))
;; => ("page" "heading" "intro" "body")

结果与您指定的不同,原因如下:

整个 HTML 片段包含一个具有data-table属性的body标记,然后包含一个具有data-table属性的div标记,
  • 但您的 HTML 片段会查看后者,因此我更改了代码以查找具有data-table属性的div标记
  • 有一个header标签,其data-field属性设置为"title"(预期字段),但它是body标签的一部分,data-table属性设置为"布局",而不是data-table属性设置为"页面"(实际字段)的div标签
  • 其余字段符合预期,但打印方式与指定字段不同,因为在许多 Lisp 语言中,(foo . (bar baz))(foo bar baz)相同,并且通常以后一种形式打印