2

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.

  • format ? Example: (format "%s" (text-properties-at (point))). – JeanPierre Dec 5 '19 at 8:19
  • No. format will return exactly the same string which also has the properties. @JeanPierre – DataHungry Dec 5 '19 at 9: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. gnu.org/software/emacs/manual/html_node/elisp/… – JeanPierre Dec 5 '19 at 11:09
  • I'm curious what your use case/goal is? – clemera Dec 5 '19 at 16:51
2

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.

| improve this answer | |
  • 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 7 '19 at 4:43
  • @DataHungry See edit. – JeanPierre Dec 7 '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 7 '19 at 15:23
  • @Drew need to use next-property-change then, isn't it? – JeanPierre Dec 7 '19 at 16:17
  • @JeanPierre: Yes, you could do that. I meant only that all of the property info is available. – Drew Dec 7 '19 at 20:40
1

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 "#")
| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.