Warning: this is an htmlized version!
The original is here, and
the conversion rules are here.
;;; tprops.el -- dealing with text with properties and saving it as sexps
;;
;; Author:  Eduardo Ochs <edrx@mat.puc-rio.br>
;; Version: 2004aug25
;; URL: http://angg.twu.net/elisp/trops.el
;;
;; This doesn't do much right now, but check the demos at the end.
;; Note that they won't work if you're in font-lock mode: font-lock
;; fontifies everything in his own way, so you'll lose the tprops
;; faces.
;;
;; (find-elnode "Saving Properties")
;; (find-elnode "Format Conversion")
;; (find-elnode "Changing Properties" "Function: propertize")
;; (find-elnode "Special Properties")
;; (find-node "(cl)Loop Basics")

;; (load "tprops.el")

;; The data types. We deal with eight data types, named
;;
;;   r0    r1   r2
;;   r0s   r1s  r2s
;;   r0sc       r2sc
;;
;; meaning:
;;
;;   r0:    "representation 0": an emacs string with constant properties
;;   r0s:   a list of r0's
;;   r0sc:  a series of r0's, concatenated; an emacs string with properties
;;   r1:    "representation 1": a string consed to a property plist
;;   r1s:   a list of r1's
;;   r2:    "representation 2": an r1 converted to text
;;   r2s:   a list of r2's
;;   r2sc:  a list of r2's, concatenated (possibly with newlines)
;;
;; Examples:
;;
;;   r0:    #("el" 0 2 (face fg:yellow))
;;   r0s:   ("H" #("el" 0 2 (face fg:yellow)) "lo")
;;   r0sc:  #("Hello" 0 1 nil 1 3 (face fg:yellow) 3 5 nil)
;;   r1:    ("el" face fg:yellow)
;;   r1s:   (("H") ("el" face fg:yellow) ("lo"))
;;   r2:    "(\"el\" face fg:yellow)"
;;   r2s:   ("(\"H\")" "(\"el\" face fg:yellow)" "(\"lo\")")
;;   r2sc:  "(\"H\")\n(\"el\" face fg:yellow)\n(\"lo\")\n"

;; Converting from internal (r0) to external (r2):
;;
(defun tp-r0-region-to-r1 (s e)
  (interactive "r")
  (cons (buffer-substring-no-properties s e)
	(text-properties-at s)))

(defun tp-r0sc-region-to-r1s (s e)
  (interactive "r")
  (let ((nextpos (next-property-change s nil e)))
    (if (and nextpos (< nextpos e))
        (cons (tp-r0-region-to-r1 s nextpos)
	      (tp-r0sc-region-to-r1s nextpos e))
      (if (< s e)
	  (list (tp-r0-region-to-r1 s e))))))

(defun tp-r1s-to-r2sc (r1s)
  (mapconcat (lambda (r1) (format "%S\n" r1)) r1s ""))

;; Converting from external (r2) to internal (r0):
;;
(defun tp-r2sc-to-r1s (r2sc)
  (read (concat "(" r2sc ")")))

(defun tp-r1-to-r0 (r1)
  (if (cdr r1) (apply 'propertize r1) (car r1)))

(defun tp-r1s-to-r0sc (r1s)
  (mapconcat 'tp-r1-to-r0 r1s ""))

;; r2sc buffer -> r0sc buffer
;;
(defun tp-r2sc-region-to-r0sc-buffer (buffername s e)
  (if (get-buffer buffername)
      (error "tp-make-r0-buffer: buffer %S exists" buffername))
  (let* ((r2sc (buffer-substring-no-properties s e))
	 (r1s (tp-r2sc-to-r1s r2sc))
	 (r0sc (tp-r1s-to-r0sc r1s))
	 (b-f-c-s buffer-file-coding-system))
    (with-current-buffer (get-buffer-create buffername)
      (set (make-local-variable 'buffer-file-coding-system) b-f-c-s)
      (insert r0sc))))

(defun tp-r0ize-buffer (buffername &optional s e)
  (tp-r2sc-region-to-r0sc-buffer
   buffername (or s (point-min)) (or e (point-max)))
  (switch-to-buffer buffername))

;; Saving the contents of a r0sc buffer into the r2sc buffer that generated it
;;
(defun tp-end-of-initial-comments ()
  (save-excursion
    (goto-char (point-min))
    (while (and (not (eobp)) (looking-at "[ \t]*\\(;\\|$\\)"))
      (forward-line 1))
    (point)))
      
(defun tp-beginning-of-final-comments ()
  (save-excursion
    (goto-char (point-max))
    (forward-line 0)
    (let ((pos (point-max)))
      (while (and (not (bobp)) (looking-at "[ \t]*\\(;\\|$\\)"))
	(setq pos (point))
	(forward-line -1))
      pos)))

(defun tp-replace-r2sc-block (newr2sc)
  (let ((s (tp-end-of-initial-comments))
	(e (tp-beginning-of-final-comments)))
    (if (> s e) (error "No r2sc part found!"))
    (delete-region s e)
    (goto-char s)
    (insert newr2sc)))

(defun tp-r2ize-buffer (buffername)
  (let* ((r1s (tp-r0sc-region-to-r1s (point-min) (point-max)))
	 (r2sc (tp-r1s-to-r2sc r1s)))
    (switch-to-buffer buffername)
    (tp-replace-r2sc-block r2sc)))


;; Demos
;; (info "(elisp)Special Properties")

(make-face 'fg:yellow)
(set-face-foreground 'fg:yellow "yellow")

(setq tp-sample-r1s '(
  ("a" face (:foreground "red"))
  (" ")
  (":=" face bold)
  (" ")
  ("2" face fg:yellow)
))

(setq tp-hello-r1s '(
  ("H")
  ("el" face fg:yellow)
  ("lo")
))

;; The tp-r1s--to-r0sc demo won't work if you're in font-lock-mode
' (insert (tp-r1s-to-r0sc tp-sample-r1s))
' (insert (tp-r1s-to-r2sc tp-sample-r1s))


;; An application:
;;
(defun tp-filter-props (filter plist)
  (if plist
      (let ((propkey (car plist))
	    (propval (cadr plist))
	    (rest (cddr plist)))
	(append (funcall filter propkey propval)
		(tp-filter-props filter rest)))))

(defun tp-r1s-filter-props (filter r1s)
  (mapcar (lambda (r1)
	    (cons (car r1) (tp-filter-props filter (cdr r1))))
	  r1s))

(defun tp-r1s-propkeys (r1s)
  (let ((propkeys ()))
    (tp-r1s-filter-props
     (lambda (key val) (add-to-list 'propkeys key))
     r1s)
    propkeys))

(defun tp-r0sc-region-propkeys (s e show-it)
  (interactive "r\np")
  (let* ((max-lisp-eval-depth 500000)
	 (max-specpdl-size 50000)
	 (r1s (tp-r0sc-region-to-r1s (min s e) (max s e))) 
	 (propkeys (tp-r1s-propkeys r1s)))
    (if show-it (message "%S" propkeys))
    propkeys))


;; r0 files <-> r2 files
;;
(defun tp-read-r2-file (fname)
  "Destroy the contents of the current file & rebuild them (r0<-r2) from FNAME" 
  (let (r1s coding (thisbuffer (current-buffer)) (pos (point)))
    (find-file fname)
    (setq r1s (read (buffer-substring (point-min) (point-max))))
    (setq coding buffer-file-coding-system)
    (switch-to-buffer thisbuffer)
    (delete-region (point-min) (point-max))
    (set-buffer-file-coding-system coding)
    (insert (tp-r1s-to-r0sc r1s))
    (goto-char pos)))

(defun tp-save-into-r2-file (fname)
  "Destroys most of FNAME and stores this file there, doing r0->r2"
  (let* ((max-lisp-eval-depth 500000)
	 (max-specpdl-size 500000)
	 (thisbuffer (current-buffer))
	 (r1s (tp-r0sc-region-to-r1s (point-min) (point-max))))
    (find-file fname)
    (goto-char (tp-end-of-initial-comments))
    (delete-region (point) (point-max))
    (save-excursion
      (insert "(\n"
	      (tp-r1s-to-r2sc r1s)
	      ")\n"))
    (switch-to-buffer thisbuffer)))


;; tp-r0-mode and tp-r2-mode
;;
(defvar tp-r0-file nil
  "The name of the associated tp-r0 file (for tp-r2 buffers)")
(defvar tp-r2-file nil
  "The name of the associated tp-r2 file (for tp-r0 buffers)")
(defvar tp-r0-status nil
  "True in a tp-r0 buffer if we have executed tp-read-r2-file")

(make-variable-buffer-local 'tp-r0-file)
(make-variable-buffer-local 'tp-r2-file)
(make-variable-buffer-local 'tp-r0-status)

(defun tp-r0-visit-r2-file ()
  (interactive)
  (find-file tp-r2-file))
(defun tp-r0-assert-properties ()
  (interactive)
  (if (null tp-r0-status)
      (tp-r0-read-r2-file)
    (message "Properties already read")))
(defun tp-r0-read-r2-file ()
  (interactive)
  (let ((bm (buffer-modified-p)))
    (tp-read-r2-file tp-r2-file)
    (if (not bm)
	(set-buffer-modified-p nil)))
  (setq tp-r0-status t)
  (message "Read %s" tp-r2-file))
(defun tp-r0-save-properties ()
  (interactive)
  (tp-save-into-r2-file tp-r2-file)
  (message "Wrote %s" tp-r2-file))

(defvar tp-r0-mode-map (make-sparse-keymap))
(define-key tp-r0-mode-map "\C-c2" 'tp-r0-visit-r2-file)
(define-key tp-r0-mode-map "\C-c\C-o" 'tp-r0-visit-r2-file)
(define-key tp-r0-mode-map "\C-c\C-a" 'tp-r0-assert-properties)
(define-key tp-r0-mode-map "\C-c\C-r" 'tp-r0-read-r2-file)
(define-key tp-r0-mode-map "\C-c\C-s" 'tp-r0-save-properties)

(define-minor-mode tp-r0-mode
  "Mode for editing a tp-r0 file"
  :init-value nil :global nil :lighter " r0")

(defun tp-r2-visit-r0-file ()
  (interactive)
  (find-file tp-r0-file)
  (tp-r0-assert-properties))
(defun tp-r2-write-r0-file ()
  (interactive)
  (let ((thisfname (buffer-file-name)) (thisbuffer (current-buffer)))
    (find-file tp-r0-file)
    (tp-read-r2-file thisfname)
    (normal-mode)
    (switch-to-buffer thisbuffer)))

(defvar tp-r2-mode-map (make-sparse-keymap))
(define-key tp-r2-mode-map "\C-c0" 'tp-r2-visit-r0-file)
(define-key tp-r2-mode-map "\C-c\C-o" 'tp-r2-visit-r0-file)
(define-key tp-r2-mode-map "\C-c\C-s" 'tp-r2-write-r0-file)

(define-minor-mode tp-r2-mode
  "Mode for editing a tp-r2 file"
  :init-value nil :global nil :lighter " r2")




(provide 'tprops)



;; (defun tpes-buffer-initial-comments ()
;;   (save-excursion
;;     (goto-char (point-min))
;;     (re-search-forward "^\\([ \t]*\\(;[^\n]*\\)?\n\\)*" nil t)
;;     (match-string 0)))



;; Local Variables:
;; coding:               raw-text-unix
;; ee-comment-prefix:    ";;"
;; modes:                (emacs-lisp-mode fundamental-mode)
;; End: