4

Is it possible to separate text properties and convert them into a string? For example, with a string with properties:

#("gfsd \"fsgfd\"

sdfdsgfs \"fsdfs\"s sfg fdg fdffffffffffffff" 0 5 (fontified t) 5 6 (fontified t syntax-table (15) face font-lock-string-face) 6 11 (fontified t face font-lock-string-face) 11 12 (fontified t syntax-table (15) face font-lock-string-face) 12 14 (fontified t) 14 23 (fontified t) 23 24 (fontified t syntax-table (15) face font-lock-string-face) 24 29 (fontified t face font-lock-string-face) 29 30 (fontified t syntax-table (15) face font-lock-string-face) 30 56 (fontified t))

I would like to extract the properties

0 5 (fontified t) 5 6 (fontified t syntax-table (15) face font-lock-string-face) 6 11 (fontified t face font-lock-string-face) 11 12 (fontified t syntax-table (15) face font-lock-string-face) 12 14 (fontified t) 14 23 (fontified t) 23 24 (fontified t syntax-table (15) face font-lock-string-face) 24 29 (fontified t face font-lock-string-face) 29 30 (fontified t syntax-table (15) face font-lock-string-face) 30 56 (fontified t)

as a plain string.

Drew
  • 77,472
  • 10
  • 114
  • 243
DataHungry
  • 247
  • 1
  • 7
  • format ? Example: (format "%s" (text-properties-at (point))). – JeanPierre Dec 05 '19 at 08:19
  • No. format will return exactly the same string which also has the properties. @JeanPierre – DataHungry Dec 05 '19 at 09:07
  • 1
    Note the text-properties-at. We get the text properties at point, then format them to a string. text-properties-at can also be applied to a string. See section 32.19.1 of elisp manual. https://www.gnu.org/software/emacs/manual/html_node/elisp/Examining-Properties.html#Examining-Properties – JeanPierre Dec 05 '19 at 11:09
  • I'm curious what your use case/goal is? – clemera Dec 05 '19 at 16:51
  • In Emacs 28, you can extract a string's text properties with the function object-intervals: https://git.savannah.gnu.org/cgit/emacs.git/commit/?id=aa7e5ce651b1872180e8da94ac80fbc25e33eec0 – Basil Dec 18 '20 at 12:59

2 Answers2

3

You can extract properties from a given position of a string or buffer with function text-properties-at. Here's its docstring (C-h f text-properties-at):

text-properties-at is a built-in function in ‘C source code’.

(text-properties-at POSITION &optional OBJECT)

Return the list of properties of the character at POSITION in OBJECT. If the optional second argument OBJECT is a buffer (or nil, which means the current buffer), POSITION is a buffer position (integer or marker). If OBJECT is a string, POSITION is a 0-based index into it. If POSITION is at the end of OBJECT, the value is nil.

Once you have the properties (it's a list) you can turn it into a string using format. For example (from @Drew):

(setq mystrg (propertize "abcd" 'foo 'foo-prop 'bar 'bar-prop)) 
(text-properties-at 0 mystrg) ; ==> (foo foo-prop bar bar-prop)
(setq prop-strg (format "%s" (text-properties-at 0 mystrg)))

For more info, see the section of elisp manual on Examining Text Properties.

However if you're interested in showing all the properties and where they apply in the string (that is, you want to get as a string the part about properties of the printed representation of the string) then you can get the printed representation with prin1-to-string then extract the part with properties (that is, after the representation of the string itself).

It turns out to be not so simple, because the printed representation differs according to the presence of properties of not, and the length of the text part varies if escapes are present. For a straight string the text representation is:

"the string"

while for a string with properties it is:

#("the string" 0 3 (properties) ...)

but can also be:

#("a string with a \" inside" 0 3 (properties) ...)

Here is a tentative function:

(defun get-string-properties-as-string (st)
  "Return a string showing the properties of ST."
  (let ((p (prin1-to-string st)))
    (if (eq (elt p 0) ?#)
    (let ((nb (length (seq-filter (lambda (c) (eq c ?\")) st))) ;; how many " in ST
          (p  (substring p 2 (1- (length p))))) ;; remove #( and )
      (loop for i from 1 to (+ 2 nb) do
        ;; remove characters up to the next "
        (setq p (seq-drop p (1+ (seq-position p ?\")))))
      p)
      ;; string with no properties
      "")))

Maybe someone will propose a better way to do that, it looks rather ugly I think.

JeanPierre
  • 7,465
  • 1
  • 20
  • 39
  • Thanks! One issue with this approach is that (if I understand it correctly) I have to probe the original string at every position to get their properties. Can I get all the properties of the string at once? something like: 0 5 (fontified t) 5 6 (fontified t syntax-table (15) face font-lock-string-face) 6 11 (fontified t face font-lock-string-face) 11 12 (fontified t syntax-table (15) face font-lock-string-face) 12 14 (fontified t) 14 23 (fontified t) 23 24 – DataHungry Dec 07 '19 at 04:43
  • @DataHungry See edit. – JeanPierre Dec 07 '19 at 13:03
  • Taking the properties of the string from its beginning to its end gives you all of the properties on that string, as well as the positions where they occur. – Drew Dec 07 '19 at 15:23
  • @Drew need to use next-property-change then, isn't it? – JeanPierre Dec 07 '19 at 16:17
  • @JeanPierre: Yes, you could do that. I meant only that all of the property info is available. – Drew Dec 07 '19 at 20:40
2

I had the same question for a long time. I don't know a "native" solution but a work-around using (format "%S" ...):

(propertize "hello" 'face 'italic)
;; => #("hello" 0 5 (face italic))

(substring (format "%S" (propertize "hello" 'face 'italic)) 1)
;; => "(\"hello\" 0 5 (face italic))"

(cdr (read (substring (format "%S" (propertize "hello" 'face 'italic)) 1)))
;; => (0 5 (face italic))

Be aware that read doesn't work all the time, for example, a buffer object has no read syntax, see (elisp) Printed Representation:

(read
 (substring
  (format "%S" (propertize "hello" 'buffer (current-buffer)))
  1))
;; error-> (invalid-read-syntax "#")
xuchunyang
  • 14,527
  • 1
  • 19
  • 39
  • 1
    Emacs 28 added a function object-intervals which returns the text properties of a string: https://git.savannah.gnu.org/cgit/emacs.git/commit/?id=aa7e5ce651b1872180e8da94ac80fbc25e33eec0 – Basil Dec 18 '20 at 12:58