Commit | Line | Data |
---|---|---|
23f87bed | 1 | ;;; html2text.el --- a simple html to plain text converter |
f4dd4ae8 | 2 | ;; Copyright (C) 2002, 2003, 2004 Free Software Foundation, Inc. |
23f87bed MB |
3 | |
4 | ;; Author: Joakim Hove <hove@phys.ntnu.no> | |
5 | ||
6 | ;; This file is part of GNU Emacs. | |
7 | ||
8 | ;; GNU Emacs is free software; you can redistribute it and/or modify | |
9 | ;; it under the terms of the GNU General Public License as published by | |
10 | ;; the Free Software Foundation; either version 2, or (at your option) | |
11 | ;; any later version. | |
12 | ||
13 | ;; GNU Emacs is distributed in the hope that it will be useful, | |
14 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
16 | ;; GNU General Public License for more details. | |
17 | ||
18 | ;; You should have received a copy of the GNU General Public License | |
19 | ;; along with GNU Emacs; see the file COPYING. If not, write to the | |
20 | ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330, | |
21 | ;; Boston, MA 02111-1307, USA. | |
22 | ||
23 | ;;; Commentary: | |
24 | ||
25 | ;; These functions provide a simple way to wash/clean html infected | |
26 | ;; mails. Definitely do not work in all cases, but some improvement | |
e2642250 | 27 | ;; in readability is generally obtained. Formatting is only done in |
23f87bed MB |
28 | ;; the buffer, so the next time you enter the article it will be |
29 | ;; "re-htmlized". | |
30 | ;; | |
e2642250 | 31 | ;; The main function is `html2text'. |
23f87bed MB |
32 | |
33 | ;;; Code: | |
34 | ||
35 | ;; | |
36 | ;; <Global variables> | |
37 | ;; | |
38 | ||
39 | (eval-when-compile | |
40 | (require 'cl)) | |
41 | ||
42 | (defvar html2text-format-single-element-list '(("hr" . html2text-clean-hr))) | |
43 | ||
44 | (defvar html2text-replace-list | |
f4dd4ae8 MB |
45 | '((" " . " ") (">" . ">") ("<" . "<") (""" . "\"") |
46 | ("&" . "&") ("'" . "'")) | |
23f87bed MB |
47 | "The map of entity to text. |
48 | ||
49 | This is an alist were each element is a dotted pair consisting of an | |
e2642250 MB |
50 | old string, and a replacement string. This replacement is done by the |
51 | function `html2text-substitute' which basically performs a | |
52 | `replace-string' operation for every element in the list. This is | |
23f87bed MB |
53 | completely verbatim - without any use of REGEXP.") |
54 | ||
55 | (defvar html2text-remove-tag-list | |
56 | '("html" "body" "p" "img" "dir" "head" "div" "br" "font" "title" "meta") | |
57 | "A list of removable tags. | |
58 | ||
59 | This is a list of tags which should be removed, without any | |
e2642250 MB |
60 | formatting. Note that tags in the list are presented *without* |
61 | any \"<\" or \">\". All occurences of a tag appearing in this | |
62 | list are removed, irrespective of whether it is a closing or | |
63 | opening tag, or if the tag has additional attributes. The | |
64 | deletion is done by the function `html2text-remove-tags'. | |
23f87bed MB |
65 | |
66 | For instance the text: | |
67 | ||
68 | \"Here comes something <font size\"+3\" face=\"Helvetica\"> big </font>.\" | |
69 | ||
70 | will be reduced to: | |
71 | ||
72 | \"Here comes something big.\" | |
73 | ||
74 | If this list contains the element \"font\".") | |
75 | ||
76 | (defvar html2text-format-tag-list | |
77 | '(("b" . html2text-clean-bold) | |
e2642250 | 78 | ("strong" . html2text-clean-bold) |
23f87bed MB |
79 | ("u" . html2text-clean-underline) |
80 | ("i" . html2text-clean-italic) | |
e2642250 | 81 | ("em" . html2text-clean-italic) |
23f87bed MB |
82 | ("blockquote" . html2text-clean-blockquote) |
83 | ("a" . html2text-clean-anchor) | |
84 | ("ul" . html2text-clean-ul) | |
85 | ("ol" . html2text-clean-ol) | |
86 | ("dl" . html2text-clean-dl) | |
87 | ("center" . html2text-clean-center)) | |
88 | "An alist of tags and processing functions. | |
89 | ||
90 | This is an alist where each dotted pair consists of a tag, and then | |
e2642250 | 91 | the name of a function to be called when this tag is found. The |
23f87bed MB |
92 | function is called with the arguments p1, p2, p3 and p4. These are |
93 | demontrated below: | |
94 | ||
95 | \"<b> This is bold text </b>\" | |
96 | ^ ^ ^ ^ | |
97 | | | | | | |
98 | p1 p2 p3 p4 | |
99 | ||
100 | Then the called function will typically format the text somewhat and | |
101 | remove the tags.") | |
102 | ||
103 | (defvar html2text-remove-tag-list2 '("li" "dt" "dd" "meta") | |
104 | "Another list of removable tags. | |
105 | ||
106 | This is a list of tags which are removed similarly to the list | |
107 | `html2text-remove-tag-list' - but these tags are retained for the | |
108 | formatting, and then moved afterward.") | |
109 | ||
110 | ;; | |
111 | ;; </Global variables> | |
112 | ;; | |
113 | ||
114 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
115 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
116 | ||
117 | ;; | |
118 | ;; <Utility functions> | |
119 | ;; | |
120 | ||
23f87bed | 121 | |
e2642250 MB |
122 | (defun html2text-replace-string (from-string to-string min max) |
123 | "Replace FROM-STRING with TO-STRING in region from MIN to MAX." | |
124 | (goto-char min) | |
23f87bed MB |
125 | (let ((delta (- (string-width to-string) (string-width from-string))) |
126 | (change 0)) | |
e2642250 | 127 | (while (search-forward from-string max t) |
23f87bed | 128 | (replace-match to-string) |
e2642250 MB |
129 | (setq change (+ change delta))) |
130 | change)) | |
23f87bed MB |
131 | |
132 | ;; | |
133 | ;; </Utility functions> | |
134 | ;; | |
135 | ||
136 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
137 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
138 | ||
139 | ;; | |
140 | ;; <Functions related to attributes> i.e. <font size=+3> | |
141 | ;; | |
142 | ||
e2642250 MB |
143 | (defun html2text-attr-value (list attribute) |
144 | "Get value of ATTRIBUTE from LIST." | |
145 | (nth 1 (assoc attribute list))) | |
23f87bed | 146 | |
0683d241 | 147 | (defun html2text-get-attr (p1 p2) |
23f87bed MB |
148 | (goto-char p1) |
149 | (re-search-forward " +[^ ]" p2 t) | |
150 | (let* ((attr-string (buffer-substring-no-properties (1- (point)) (1- p2))) | |
151 | (tmp-list (split-string attr-string)) | |
152 | (attr-list) | |
153 | (counter 0) | |
154 | (prev (car tmp-list)) | |
155 | (this (nth 1 tmp-list)) | |
156 | (next (nth 2 tmp-list)) | |
157 | (index 1)) | |
158 | ||
159 | (cond | |
160 | ;; size=3 | |
161 | ((string-match "[^ ]=[^ ]" prev) | |
162 | (let ((attr (nth 0 (split-string prev "="))) | |
163 | (value (nth 1 (split-string prev "=")))) | |
e2642250 | 164 | (setq attr-list (cons (list attr value) attr-list)))) |
23f87bed MB |
165 | ;; size= 3 |
166 | ((string-match "[^ ]=\\'" prev) | |
e2642250 | 167 | (setq attr-list (cons (list (substring prev 0 -1) this) attr-list)))) |
23f87bed MB |
168 | |
169 | (while (< index (length tmp-list)) | |
170 | (cond | |
171 | ;; size=3 | |
172 | ((string-match "[^ ]=[^ ]" this) | |
173 | (let ((attr (nth 0 (split-string this "="))) | |
174 | (value (nth 1 (split-string this "=")))) | |
e2642250 | 175 | (setq attr-list (cons (list attr value) attr-list)))) |
23f87bed MB |
176 | ;; size =3 |
177 | ((string-match "\\`=[^ ]" this) | |
178 | (setq attr-list (cons (list prev (substring this 1)) attr-list))) | |
23f87bed MB |
179 | ;; size= 3 |
180 | ((string-match "[^ ]=\\'" this) | |
e2642250 | 181 | (setq attr-list (cons (list (substring this 0 -1) next) attr-list))) |
23f87bed MB |
182 | ;; size = 3 |
183 | ((string= "=" this) | |
e2642250 | 184 | (setq attr-list (cons (list prev next) attr-list)))) |
23f87bed MB |
185 | (setq index (1+ index)) |
186 | (setq prev this) | |
187 | (setq this next) | |
e2642250 | 188 | (setq next (nth (1+ index) tmp-list))) |
23f87bed MB |
189 | ;; |
190 | ;; Tags with no accompanying "=" i.e. value=nil | |
191 | ;; | |
192 | (setq prev (car tmp-list)) | |
193 | (setq this (nth 1 tmp-list)) | |
194 | (setq next (nth 2 tmp-list)) | |
195 | (setq index 1) | |
196 | ||
e2642250 MB |
197 | (when (and (not (string-match "=" prev)) |
198 | (not (string= (substring this 0 1) "="))) | |
199 | (setq attr-list (cons (list prev nil) attr-list))) | |
23f87bed | 200 | (while (< index (1- (length tmp-list))) |
e2642250 MB |
201 | (when (and (not (string-match "=" this)) |
202 | (not (or (string= (substring next 0 1) "=") | |
203 | (string= (substring prev -1) "=")))) | |
204 | (setq attr-list (cons (list this nil) attr-list))) | |
23f87bed MB |
205 | (setq index (1+ index)) |
206 | (setq prev this) | |
207 | (setq this next) | |
e2642250 MB |
208 | (setq next (nth (1+ index) tmp-list))) |
209 | ||
210 | (when (and this | |
211 | (not (string-match "=" this)) | |
212 | (not (string= (substring prev -1) "="))) | |
213 | (setq attr-list (cons (list this nil) attr-list))) | |
214 | ;; return - value | |
215 | attr-list)) | |
23f87bed MB |
216 | |
217 | ;; | |
218 | ;; </Functions related to attributes> | |
219 | ;; | |
220 | ||
221 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
222 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
223 | ||
224 | ;; | |
225 | ;; <Functions to be called to format a tag-pair> | |
226 | ;; | |
227 | (defun html2text-clean-list-items (p1 p2 list-type) | |
228 | (goto-char p1) | |
229 | (let ((item-nr 0) | |
230 | (items 0)) | |
231 | (while (re-search-forward "<li>" p2 t) | |
232 | (setq items (1+ items))) | |
233 | (goto-char p1) | |
234 | (while (< item-nr items) | |
235 | (setq item-nr (1+ item-nr)) | |
236 | (re-search-forward "<li>" (point-max) t) | |
237 | (cond | |
238 | ((string= list-type "ul") (insert " o ")) | |
239 | ((string= list-type "ol") (insert (format " %s: " item-nr))) | |
e2642250 | 240 | (t (insert " x ")))))) |
23f87bed MB |
241 | |
242 | (defun html2text-clean-dtdd (p1 p2) | |
243 | (goto-char p1) | |
244 | (let ((items 0) | |
245 | (item-nr 0)) | |
246 | (while (re-search-forward "<dt>" p2 t) | |
247 | (setq items (1+ items))) | |
248 | (goto-char p1) | |
249 | (while (< item-nr items) | |
250 | (setq item-nr (1+ item-nr)) | |
251 | (re-search-forward "<dt>\\([ ]*\\)" (point-max) t) | |
252 | (when (match-string 1) | |
253 | (delete-region (point) (- (point) (string-width (match-string 1))))) | |
254 | (let ((def-p1 (point)) | |
255 | (def-p2 0)) | |
256 | (re-search-forward "\\([ ]*\\)\\(</dt>\\|<dd>\\)" (point-max) t) | |
257 | (if (match-string 1) | |
258 | (progn | |
259 | (let* ((mw1 (string-width (match-string 1))) | |
260 | (mw2 (string-width (match-string 2))) | |
261 | (mw (+ mw1 mw2))) | |
262 | (goto-char (- (point) mw)) | |
263 | (delete-region (point) (+ (point) mw1)) | |
264 | (setq def-p2 (point)))) | |
265 | (setq def-p2 (- (point) (string-width (match-string 2))))) | |
266 | (put-text-property def-p1 def-p2 'face 'bold))))) | |
267 | ||
268 | (defun html2text-delete-tags (p1 p2 p3 p4) | |
269 | (delete-region p1 p2) | |
270 | (delete-region (- p3 (- p2 p1)) (- p4 (- p2 p1)))) | |
271 | ||
272 | (defun html2text-delete-single-tag (p1 p2) | |
273 | (delete-region p1 p2)) | |
274 | ||
275 | (defun html2text-clean-hr (p1 p2) | |
276 | (html2text-delete-single-tag p1 p2) | |
277 | (goto-char p1) | |
278 | (newline 1) | |
e2642250 | 279 | (insert (make-string fill-column ?-))) |
23f87bed MB |
280 | |
281 | (defun html2text-clean-ul (p1 p2 p3 p4) | |
282 | (html2text-delete-tags p1 p2 p3 p4) | |
e2642250 | 283 | (html2text-clean-list-items p1 (- p3 (- p1 p2)) "ul")) |
23f87bed MB |
284 | |
285 | (defun html2text-clean-ol (p1 p2 p3 p4) | |
286 | (html2text-delete-tags p1 p2 p3 p4) | |
e2642250 | 287 | (html2text-clean-list-items p1 (- p3 (- p1 p2)) "ol")) |
23f87bed MB |
288 | |
289 | (defun html2text-clean-dl (p1 p2 p3 p4) | |
290 | (html2text-delete-tags p1 p2 p3 p4) | |
e2642250 | 291 | (html2text-clean-dtdd p1 (- p3 (- p1 p2)))) |
23f87bed MB |
292 | |
293 | (defun html2text-clean-center (p1 p2 p3 p4) | |
294 | (html2text-delete-tags p1 p2 p3 p4) | |
e2642250 | 295 | (center-region p1 (- p3 (- p2 p1)))) |
23f87bed MB |
296 | |
297 | (defun html2text-clean-bold (p1 p2 p3 p4) | |
298 | (put-text-property p2 p3 'face 'bold) | |
e2642250 | 299 | (html2text-delete-tags p1 p2 p3 p4)) |
23f87bed MB |
300 | |
301 | (defun html2text-clean-title (p1 p2 p3 p4) | |
302 | (put-text-property p2 p3 'face 'bold) | |
e2642250 | 303 | (html2text-delete-tags p1 p2 p3 p4)) |
23f87bed MB |
304 | |
305 | (defun html2text-clean-underline (p1 p2 p3 p4) | |
306 | (put-text-property p2 p3 'face 'underline) | |
e2642250 | 307 | (html2text-delete-tags p1 p2 p3 p4)) |
23f87bed MB |
308 | |
309 | (defun html2text-clean-italic (p1 p2 p3 p4) | |
310 | (put-text-property p2 p3 'face 'italic) | |
e2642250 | 311 | (html2text-delete-tags p1 p2 p3 p4)) |
23f87bed MB |
312 | |
313 | (defun html2text-clean-font (p1 p2 p3 p4) | |
e2642250 | 314 | (html2text-delete-tags p1 p2 p3 p4)) |
23f87bed MB |
315 | |
316 | (defun html2text-clean-blockquote (p1 p2 p3 p4) | |
e2642250 | 317 | (html2text-delete-tags p1 p2 p3 p4)) |
23f87bed MB |
318 | |
319 | (defun html2text-clean-anchor (p1 p2 p3 p4) | |
e2642250 MB |
320 | ;; If someone can explain how to make the URL clickable I will surely |
321 | ;; improve upon this. | |
322 | ;; Maybe `goto-addr.el' can be used here. | |
0683d241 | 323 | (let* ((attr-list (html2text-get-attr p1 p2)) |
23f87bed MB |
324 | (href (html2text-attr-value attr-list "href"))) |
325 | (delete-region p1 p4) | |
326 | (when href | |
327 | (goto-char p1) | |
328 | (insert (substring href 1 -1 )) | |
329 | (put-text-property p1 (point) 'face 'bold)))) | |
330 | ||
331 | ;; | |
332 | ;; </Functions to be called to format a tag-pair> | |
333 | ;; | |
334 | ||
335 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
336 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
337 | ||
338 | ;; | |
339 | ;; <Functions to be called to fix up paragraphs> | |
340 | ;; | |
341 | ||
342 | (defun html2text-fix-paragraph (p1 p2) | |
343 | (goto-char p1) | |
344 | (let ((has-br-line) | |
345 | (refill-start) | |
346 | (refill-stop)) | |
e2642250 MB |
347 | (when (re-search-forward "<br>$" p2 t) |
348 | (goto-char p1) | |
349 | (when (re-search-forward ".+[^<][^b][^r][^>]$" p2 t) | |
350 | (beginning-of-line) | |
351 | (setq refill-start (point)) | |
352 | (goto-char p2) | |
353 | (re-search-backward ".+[^<][^b][^r][^>]$" refill-start t) | |
354 | (next-line 1) | |
355 | (end-of-line) | |
356 | ;; refill-stop should ideally be adjusted to | |
357 | ;; accomodate the "<br>" strings which are removed | |
358 | ;; between refill-start and refill-stop. Can simply | |
359 | ;; be returned from my-replace-string | |
360 | (setq refill-stop (+ (point) | |
361 | (html2text-replace-string | |
362 | "<br>" "" | |
363 | refill-start (point)))) | |
364 | ;; (message "Point = %s refill-stop = %s" (point) refill-stop) | |
365 | ;; (sleep-for 4) | |
366 | (fill-region refill-start refill-stop)))) | |
367 | (html2text-replace-string "<br>" "" p1 p2)) | |
23f87bed MB |
368 | |
369 | ;; | |
370 | ;; This one is interactive ... | |
371 | ;; | |
372 | (defun html2text-fix-paragraphs () | |
373 | "This _tries_ to fix up the paragraphs - this is done in quite a ad-hook | |
374 | fashion, quite close to pure guess-work. It does work in some cases though." | |
375 | (interactive) | |
86713405 | 376 | (goto-char (point-min)) |
23f87bed MB |
377 | (replace-regexp "^<br>$" "") |
378 | ;; Removing lonely <br> on a single line, if they are left intact we | |
379 | ;; dont have any paragraphs at all. | |
86713405 | 380 | (goto-char (point-min)) |
23f87bed MB |
381 | (while (not (eobp)) |
382 | (let ((p1 (point))) | |
383 | (forward-paragraph 1) | |
384 | ;;(message "Kaller fix med p1=%s p2=%s " p1 (1- (point))) (sleep-for 5) | |
385 | (html2text-fix-paragraph p1 (1- (point))) | |
386 | (goto-char p1) | |
387 | (when (not (eobp)) | |
388 | (forward-paragraph 1))))) | |
389 | ||
390 | ;; | |
391 | ;; </Functions to be called to fix up paragraphs> | |
392 | ;; | |
393 | ||
394 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
395 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
396 | ||
397 | ;; | |
398 | ;; <Interactive functions> | |
399 | ;; | |
400 | ||
401 | (defun html2text-remove-tags (tag-list) | |
e2642250 | 402 | "Removes the tags listed in the list `html2text-remove-tag-list'. |
23f87bed MB |
403 | See the documentation for that variable." |
404 | (interactive) | |
405 | (dolist (tag tag-list) | |
86713405 | 406 | (goto-char (point-min)) |
23f87bed MB |
407 | (while (re-search-forward (format "\\(</?%s[^>]*>\\)" tag) (point-max) t) |
408 | (delete-region (match-beginning 0) (match-end 0))))) | |
409 | ||
410 | (defun html2text-format-tags () | |
e2642250 | 411 | "See the variable `html2text-format-tag-list' for documentation." |
23f87bed MB |
412 | (interactive) |
413 | (dolist (tag-and-function html2text-format-tag-list) | |
414 | (let ((tag (car tag-and-function)) | |
415 | (function (cdr tag-and-function))) | |
86713405 | 416 | (goto-char (point-min)) |
23f87bed MB |
417 | (while (re-search-forward (format "\\(<%s\\( [^>]*\\)?>\\)" tag) |
418 | (point-max) t) | |
419 | (let ((p1) | |
420 | (p2 (point)) | |
0683d241 | 421 | (p3) (p4)) |
23f87bed MB |
422 | (search-backward "<" (point-min) t) |
423 | (setq p1 (point)) | |
424 | (re-search-forward (format "</%s>" tag) (point-max) t) | |
425 | (setq p4 (point)) | |
426 | (search-backward "</" (point-min) t) | |
427 | (setq p3 (point)) | |
428 | (funcall function p1 p2 p3 p4) | |
e2642250 | 429 | (goto-char p1)))))) |
23f87bed MB |
430 | |
431 | (defun html2text-substitute () | |
e2642250 | 432 | "See the variable `html2text-replace-list' for documentation." |
23f87bed MB |
433 | (interactive) |
434 | (dolist (e html2text-replace-list) | |
86713405 | 435 | (goto-char (point-min)) |
23f87bed MB |
436 | (let ((old-string (car e)) |
437 | (new-string (cdr e))) | |
e2642250 | 438 | (html2text-replace-string old-string new-string (point-min) (point-max))))) |
23f87bed MB |
439 | |
440 | (defun html2text-format-single-elements () | |
23f87bed MB |
441 | (interactive) |
442 | (dolist (tag-and-function html2text-format-single-element-list) | |
443 | (let ((tag (car tag-and-function)) | |
444 | (function (cdr tag-and-function))) | |
86713405 | 445 | (goto-char (point-min)) |
23f87bed MB |
446 | (while (re-search-forward (format "\\(<%s\\( [^>]*\\)?>\\)" tag) |
447 | (point-max) t) | |
448 | (let ((p1) | |
449 | (p2 (point))) | |
450 | (search-backward "<" (point-min) t) | |
451 | (setq p1 (point)) | |
e2642250 | 452 | (funcall function p1 p2)))))) |
23f87bed MB |
453 | |
454 | ;; | |
455 | ;; Main function | |
456 | ;; | |
457 | ||
458 | ;;;###autoload | |
459 | (defun html2text () | |
460 | "Convert HTML to plain text in the current buffer." | |
461 | (interactive) | |
462 | (save-excursion | |
463 | (let ((case-fold-search t) | |
464 | (buffer-read-only)) | |
465 | (html2text-remove-tags html2text-remove-tag-list) | |
466 | (html2text-format-tags) | |
467 | (html2text-remove-tags html2text-remove-tag-list2) | |
468 | (html2text-substitute) | |
469 | (html2text-format-single-elements) | |
470 | (html2text-fix-paragraphs)))) | |
471 | ||
472 | ;; | |
473 | ;; </Interactive functions> | |
474 | ;; | |
e2642250 | 475 | (provide 'html2text) |
23f87bed MB |
476 | ;;; arch-tag: e9e57b79-35d4-4de1-a647-e7e01fe56d1e |
477 | ;;; html2text.el ends here |