Subject: Re: bug#44614: 26.3; bibtex convert case undefined

From: Francesco Potortì <[email-masked]>
Date: Sat, 14 Nov 2020 19:47:36 +0100

>> An old version of bibtex.el (last copyright 1997) uniquified cases based
>> on the bibtex-unify-case-convert variable, which is now removed.
>>
>> This is unfortunate, and in my opinion a bug.
>
>I tried to poke around to see whether there's any reason the variable
>was removed, but I can't find any mention of it in the Emacs tree, so
>it's possible that the in-tree version of bibtex.el never had the
>variable?

Mh. I found a very old version that i had set aside with the name
bibtex.el.orig, which does not contain that variable, so it may well be
that I had introduced that variable myself as a local change with the
aim of pushing it in the Emacs tree but then I forgot to do that.  It
was a long time ago, so I may well have forgotten.

Anyway, here is my patch, which I think does not make any harm.  It
changes the behaviour slightly in what I consider it being a more
consistent way.  When unifying is requested, in the default case, which
is 'identity, if an unknown field is found it is not unified (rather
than being downcased as it is now): only known fields are unified to the
known case.

Note that the last hunk is only a change of 'if' with 'when', it is big
because of reindentation and just a matter of aesthetics (or clarity).

*** bibtex-2019.el	2020-11-13 11:54:34.000000000 +0100
--- bibtex.el	2020-11-14 19:05:48.000000000 +0100
***************
*** 89,92 ****
--- 89,103 ----
  (put 'bibtex-include-OPTkey 'risky-local-variable t)
  
+ (defcustom bibtex-unify-case-convert 'identity
+   "*Function called when unifying case on entry and field names.
+ This variable is buffer-local."
+   :group 'bibtex
+   :type '(choice (const :tag "Same case as in `bibtex-field-alist'" identity)
+ 		 (const :tag "Downcase" downcase)
+ 		 (const :tag "Capitalize" capitalize)
+ 		 (const :tag "Upcase" upcase)
+ 		 (function :tag "Conversion function")))
+ (make-variable-buffer-local 'bibtex-unify-case-convert)
+ 
  (defcustom bibtex-user-optional-fields
    '(("annote" "Personal annotation (ignored)"))
***************
*** 123,127 ****
  delimiters          Change delimiters according to variables
                        `bibtex-field-delimiters' and `bibtex-entry-delimiters'.
! unify-case          Change case of entry types and field names.
  braces              Enclose parts of field entries by braces according to
                        `bibtex-field-braces-alist'.
--- 134,139 ----
  delimiters          Change delimiters according to variables
                        `bibtex-field-delimiters' and `bibtex-entry-delimiters'.
! unify-case          Change case of entry and field names according to
!                       `bibtex-unify-case-convert'.
  braces              Enclose parts of field entries by braces according to
                        `bibtex-field-braces-alist'.
***************
*** 2309,2313 ****
                  (when (memq 'unify-case format)
                    (delete-region beg-type end-type)
!                   (insert (car entry-list)))
  
                  ;; update left entry delimiter
--- 2321,2325 ----
                  (when (memq 'unify-case format)
                    (delete-region beg-type end-type)
!                   (insert (funcall bibtex-unify-case-convert (car entry-list))))
  
                  ;; update left entry delimiter
***************
*** 2510,2523 ****
  
                      ;; unify case of field name
!                     (if (memq 'unify-case format)
!                         (let ((fname (car (assoc-string field-name
!                                                         default-field-list t))))
!                           (if fname
!                               (progn
!                                 (delete-region beg-name end-name)
!                                 (goto-char beg-name)
!                                 (insert fname))
!                             ;; there are no rules we could follow
!                             (downcase-region beg-name end-name))))
  
                      ;; update point
--- 2522,2533 ----
  
                      ;; unify case of field name
!                     (when (memq 'unify-case format)
! 		      (let ((fname (car (assoc-string field-name
! 						      default-field-list t)))
! 			    (curname (buffer-substring beg-name end-name)))
! 			(delete-region beg-name end-name)
! 			(goto-char beg-name)
! 			(insert (funcall bibtex-unify-case-convert
! 					 (or fname curname)))))
  
                      ;; update point
***************
*** 2525,2554 ****
  
                ;; check whether all required fields are present
!               (if (memq 'required-fields format)
!                   (let ((alt-expect (make-vector num-alt nil))
!                         (alt-found (make-vector num-alt 0)))
!                     (dolist (fname req-field-list)
!                       (cond ((setq idx (nth 3 fname))
!                              ;; t if field has alternative flag
!                              (bibtex-vec-push alt-expect idx (car fname))
!                              (if (member-ignore-case (car fname) field-list)
!                                  (bibtex-vec-incr alt-found idx)))
!                             ((not (member-ignore-case (car fname) field-list))
!                              ;; If we use the crossref field, a required field
!                              ;; can have the OPT prefix.  So if it was empty,
!                              ;; we have deleted by now.  Nonetheless we can
!                              ;; move point on this empty field.
!                              (setq error-field-name (car fname))
!                              (error "Mandatory field `%s' is missing" (car fname)))))
!                     (dotimes (idx num-alt)
!                       (cond ((= 0 (aref alt-found idx))
!                              (setq error-field-name (car (last (aref alt-fields idx))))
!                              (error "Alternative mandatory field `%s' is missing"
!                                     (aref alt-expect idx)))
!                             ((< 1 (aref alt-found idx))
!                              (setq error-field-name (car (last (aref alt-fields idx))))
!                              (error "Alternative fields `%s' are defined %s times"
!                                     (aref alt-expect idx)
!                                     (length (aref alt-fields idx))))))))
  
                ;; update comma after last field
--- 2535,2564 ----
  
                ;; check whether all required fields are present
!               (when (memq 'required-fields format)
! 		(let ((alt-expect (make-vector num-alt nil))
! 		      (alt-found (make-vector num-alt 0)))
! 		  (dolist (fname req-field-list)
! 		    (cond ((setq idx (nth 3 fname))
! 			   ;; t if field has alternative flag
! 			   (bibtex-vec-push alt-expect idx (car fname))
! 			   (if (member-ignore-case (car fname) field-list)
! 			       (bibtex-vec-incr alt-found idx)))
! 			  ((not (member-ignore-case (car fname) field-list))
! 			   ;; If we use the crossref field, a required field
! 			   ;; can have the OPT prefix.  So if it was empty,
! 			   ;; we have deleted by now.  Nonetheless we can
! 			   ;; move point on this empty field.
! 			   (setq error-field-name (car fname))
! 			   (error "Mandatory field `%s' is missing" (car fname)))))
! 		  (dotimes (idx num-alt)
! 		    (cond ((= 0 (aref alt-found idx))
! 			   (setq error-field-name (car (last (aref alt-fields idx))))
! 			   (error "Alternative mandatory field `%s' is missing"
! 				  (aref alt-expect idx)))
! 			  ((< 1 (aref alt-found idx))
! 			   (setq error-field-name (car (last (aref alt-fields idx))))
! 			   (error "Alternative fields `%s' are defined %s times"
! 				  (aref alt-expect idx)
! 				  (length (aref alt-fields idx))))))))
  
                ;; update comma after last field