6

all. I was wondering if Emacs lisp had a built-in function for checking if a string is made entirely out of capitalized characters. Here is what I'm using right now:

(setq capital-letters (string-to-list "ABCDEFGHIJKLMNOPQRSTUVWXYZ"))

(defun chars-are-capitalized (list-of-characters)
  "Returns true if every character in a list of characters is a capital         
letter. As a special case, the empty list returns true."
  (cond
   ((equal list-of-characters nil) t)
   ((not (member (car list-of-characters) capital-letters)) nil)
   (t (chars-are-capitalized (cdr list-of-characters)))))

(defun string-is-capitalized (string)
  "Returns true if every character in a string is a capital letter. The         
empty string returns true."
  (chars-are-capitalized (string-to-list string)))

It works ok (although it relies on the assumption that I'll only be using ASCII characters), but I was wondering if I was missing some obvious function that I should know about.

Masterofpsi
  • 769
  • 6
  • 17

4 Answers4

13

In reference to other answers:

  1. Using upcase is not a good idea: it will allocate a new string, it will not find if the string has non-alphabetic characters (it seems that you want to forbid that), and it works on integers too (which Emacs uses for characters).

  2. Using string-match is better -- it fixes all of these issues. As Trey shows, you need to do that when case-fold-search is nil otherwise Emacs might treat it as a case-insensitive search. But string-match-p is even better since it avoids changing the match data. (Emacs keeps that data around after any match, and if you use string-match then you'll overwrite it, which might break code that uses your function.)

  3. Another issue is the regexp itself. Using "^...$" means that Emacs will look for some line with a matching content -- and if your string has newline characters, this might make it return a bogus result. You need to use backslash-unquote and backslash-quote which match only the beginning and end of the string.

So a correct version is:

(defun string-is-capitalized (str)
  (let ((case-fold-search nil))
    (string-match-p "\\`[A-Z]*\\'" str)))

(BTW, the usual convention in Emacs Lisp is to use a -p for predicates, as in string-capitalized-p.)

Eli Barzilay
  • 29,301
  • 3
  • 67
  • 110
  • Rather than `A-Z` I think you want to use `[:upper:]` in the regex. – Stefan Jan 06 '15 at 15:58
  • Well, (1) I dislike `[:upper:]` since it tends to mean different things (any unicode uppercase char in some, only A-Z in others) so I prefer the more explicit forms when possible; (2) I answered the question as originally asked, and that code was limited to just `A-Z`; (3) the use of a regexp is good enough so random bypassers would naturally use something else when needed; (4) finally, note that I used "*a* correct version" and avoided a regexp discussion to make it more focused... – Eli Barzilay Jan 06 '15 at 22:55
7

I don't know of a built-in function that does what you want, but this does:

(defun string-all-caps-p (string)
  "Return non-nil iff STRING is all capital letters."
  (save-match-data
    (let ((case-fold-search nil))
      (string-match "\\`[A-Z]+\\'" string))))

Edit: Changed to use ` and ' as per Eli Barzilay's feedback.

This one lets there be non A-Z chars (not what you asked for, but perhaps interesting):

(defun string-has-no-lowercase (string)
  "Return true iff STRING has no lowercase"
  (equal (upcase string) string))
Community
  • 1
  • 1
Trey Jackson
  • 73,529
  • 11
  • 197
  • 229
  • That first one makes sense; I should have thought of a regex. I'd already thought of checking (equal string (upcase string)), but I do need it to return false if the string has any non-alphabetical letters. – Masterofpsi Jan 25 '10 at 02:43
  • 2
    For the first one, you probably want to wrap that in a `save-match-data` call, so any existing regex match data doesn't get overwritten. – haxney Jan 25 '10 at 02:50
5

External string manipulation library s.el has s-uppercase?:

(s-uppercase "GOT TO. THIS AMERICA, MAN.") ; t
(s-uppercase "You cannot lose if you do not play.") ; nil

It is implemented like that:

(defun s-uppercase? (s)
  (let ((case-fold-search nil))
    (not (string-match-p "[[:lower:]]" s))))

[[:lower:]] is an Emacs-specific regex corresponding to a lowercase character. string-match-p accepts a regex and returns an index, starting from which the regex is matched, or returns nil if there's no match. The idea is to search for a lowercase character in a string, and if none is found, return t. However string-match-p ignores case by default, therefore you should temporarily turn off case-fold-search.

Emacs uses dynamic binding by default, so you can temporarily set global variables to different values inside let expression. If you set binding to lexical, let would introduce a local copy of case-fold-search, shadowing the global variable, therefore the above code wouldn't work.

Mirzhan Irkegulov
  • 17,660
  • 12
  • 105
  • 166
  • This cleared up a major point of confusion for me, thank you. One minor comment: on my system this function works regardless of whether the function is lexically or dynamically scoped. I wonder if `case-fold-search` is a so-called special variable, which means that is will continue to be dynamically bound. (See https://www.gnu.org/software/emacs/manual/html_node/elisp/Using-Lexical-Binding.html for details.) – dpritch Jul 20 '20 at 03:32
0

just a wild guess, but what if you make a copy of the string, upcase it (i dont really know much about lisp but a quick google search told there is a "upcase" function, and then check if the two strings are the same? If the are, then the original one must be in all upperscase :P

Francisco Noriega
  • 13,725
  • 11
  • 47
  • 72