emacs-lisp/package.el (package--read-pkg-desc): Fix tar-desc reference.
[emacs.git] / lisp / dom.el
blob11357e88804bd92254c4e574831aa268121349f4
1 ;;; dom.el --- XML/HTML (etc.) DOM manipulation and searching functions
3 ;; Copyright (C) 2014-2015 Free Software Foundation, Inc.
5 ;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
6 ;; Keywords: xml, html
8 ;; This file is part of GNU Emacs.
10 ;; GNU Emacs is free software: you can redistribute it and/or modify
11 ;; it under the terms of the GNU General Public License as published by
12 ;; the Free Software Foundation, either version 3 of the License, or
13 ;; (at your option) any later version.
15 ;; GNU Emacs is distributed in the hope that it will be useful,
16 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 ;; GNU General Public License for more details.
20 ;; You should have received a copy of the GNU General Public License
21 ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
23 ;;; Commentary:
25 ;;; Code:
27 (require 'cl-lib)
29 (defsubst dom-tag (node)
30 "Return the NODE tag."
31 ;; Called on a list of nodes. Use the first.
32 (if (consp (car node))
33 (caar node)
34 (car node)))
36 (defsubst dom-attributes (node)
37 "Return the NODE attributes."
38 ;; Called on a list of nodes. Use the first.
39 (if (consp (car node))
40 (cadr (car node))
41 (cadr node)))
43 (defsubst dom-children (node)
44 "Return the NODE children."
45 ;; Called on a list of nodes. Use the first.
46 (if (consp (car node))
47 (cddr (car node))
48 (cddr node)))
50 (defun dom-non-text-children (node)
51 "Return all non-text-node children of NODE."
52 (cl-loop for child in (dom-children node)
53 unless (stringp child)
54 collect child))
56 (defun dom-set-attributes (node attributes)
57 "Set the attributes of NODE to ATTRIBUTES."
58 (setq node (dom-ensure-node node))
59 (setcar (cdr node) attributes))
61 (defun dom-set-attribute (node attribute value)
62 "Set ATTRIBUTE in NODE to VALUE."
63 (setq node (dom-ensure-node node))
64 (let ((old (assoc attribute (cadr node))))
65 (if old
66 (setcdr old value)
67 (setcar (cdr node) (nconc (cadr node) (list (cons attribute value)))))))
69 (defmacro dom-attr (node attr)
70 "Return the attribute ATTR from NODE.
71 A typical attribute is `href'."
72 `(cdr (assq ,attr (dom-attributes ,node))))
74 (defun dom-text (node)
75 "Return all the text bits in the current node concatenated."
76 (mapconcat 'identity (cl-remove-if-not 'stringp (dom-children node)) " "))
78 (defun dom-texts (node &optional separator)
79 "Return all textual data under NODE concatenated with SEPARATOR in-between."
80 (mapconcat
81 'identity
82 (mapcar
83 (lambda (elem)
84 (if (stringp elem)
85 elem
86 (dom-texts elem separator)))
87 (dom-children node))
88 (or separator " ")))
90 (defun dom-child-by-tag (dom tag)
91 "Return the first child of DOM that is of type TAG."
92 (assoc tag (dom-children dom)))
94 (defun dom-by-tag (dom tag)
95 "Return elements in DOM that is of type TAG.
96 A name is a symbol like `td'."
97 (let ((matches (cl-loop for child in (dom-children dom)
98 for matches = (and (not (stringp child))
99 (dom-by-tag child tag))
100 when matches
101 append matches)))
102 (if (equal (dom-tag dom) tag)
103 (cons dom matches)
104 matches)))
106 (defun dom-strings (dom)
107 "Return elements in DOM that are strings."
108 (cl-loop for child in (dom-children dom)
109 if (stringp child)
110 collect child
111 else
112 append (dom-strings child)))
114 (defun dom-by-class (dom match)
115 "Return elements in DOM that have a class name that matches regexp MATCH."
116 (dom-elements dom 'class match))
118 (defun dom-by-style (dom match)
119 "Return elements in DOM that have a style that matches regexp MATCH."
120 (dom-elements dom 'style match))
122 (defun dom-by-id (dom match)
123 "Return elements in DOM that have an ID that matches regexp MATCH."
124 (dom-elements dom 'id match))
126 (defun dom-elements (dom attribute match)
127 "Find elements matching MATCH (a regexp) in ATTRIBUTE.
128 ATTRIBUTE would typically be `class', `id' or the like."
129 (let ((matches (cl-loop for child in (dom-children dom)
130 for matches = (and (not (stringp child))
131 (dom-elements child attribute
132 match))
133 when matches
134 append matches))
135 (attr (dom-attr dom attribute)))
136 (if (and attr
137 (string-match match attr))
138 (cons dom matches)
139 matches)))
141 (defun dom-parent (dom node)
142 "Return the parent of NODE in DOM."
143 (if (memq node (dom-children dom))
145 (let ((result nil))
146 (dolist (elem (dom-children dom))
147 (when (and (not result)
148 (not (stringp elem)))
149 (setq result (dom-parent elem node))))
150 result)))
152 (defun dom-node (tag &optional attributes &rest children)
153 "Return a DOM node with TAG and ATTRIBUTES."
154 (if children
155 `(,tag ,attributes ,@children)
156 (list tag attributes)))
158 (defun dom-append-child (node child)
159 "Append CHILD to the end of NODE's children."
160 (setq node (dom-ensure-node node))
161 (nconc node (list child)))
163 (defun dom-add-child-before (node child &optional before)
164 "Add CHILD to NODE's children before child BEFORE.
165 If BEFORE is nil, make CHILD NODE's first child."
166 (setq node (dom-ensure-node node))
167 (let ((children (dom-children node)))
168 (when (and before
169 (not (memq before children)))
170 (error "%s does not exist as a child" before))
171 (let ((pos (if before
172 (cl-position before children)
173 0)))
174 (if (zerop pos)
175 ;; First child.
176 (setcdr (cdr node) (cons child (cddr node)))
177 (setcdr (nthcdr (1- pos) children)
178 (cons child (nthcdr pos children))))))
179 node)
181 (defun dom-ensure-node (node)
182 "Ensure that NODE is a proper DOM node."
183 ;; Add empty attributes, if none.
184 (when (consp (car node))
185 (setq node (car node)))
186 (when (= (length node) 1)
187 (setcdr node (list nil)))
188 node)
190 (defun dom-pp (dom &optional remove-empty)
191 "Pretty-print DOM at point.
192 If REMOVE-EMPTY, ignore textual nodes that contain just
193 white-space."
194 (let ((column (current-column)))
195 (insert (format "(%S " (dom-tag dom)))
196 (let* ((attr (dom-attributes dom))
197 (times (length attr))
198 (column (1+ (current-column))))
199 (if (null attr)
200 (insert "nil")
201 (insert "(")
202 (dolist (elem attr)
203 (insert (format "(%S . %S)" (car elem) (cdr elem)))
204 (if (zerop (cl-decf times))
205 (insert ")")
206 (insert "\n" (make-string column ? ))))))
207 (let* ((children (if remove-empty
208 (cl-remove-if
209 (lambda (child)
210 (and (stringp child)
211 (string-match "\\`[\n\r\t  ]*\\'" child)))
212 (dom-children dom))
213 (dom-children dom)))
214 (times (length children)))
215 (if (null children)
216 (insert ")")
217 (insert "\n" (make-string (1+ column) ? ))
218 (dolist (child children)
219 (if (stringp child)
220 (if (or (not remove-empty)
221 (not (string-match "\\`[\n\r\t  ]*\\'" child)))
222 (insert (format "%S" child)))
223 (dom-pp child remove-empty))
224 (if (zerop (cl-decf times))
225 (insert ")")
226 (insert "\n" (make-string (1+ column) ? ))))))))
228 (provide 'dom)
230 ;;; dom.el ends here