org-export: Do not treat unpaired ' and " as smart quotes
* lisp/ox.el (org-export--smart-quote-status): When quotes are not balanced, treat " literally and ' as apostrophes. * testing/lisp/test-ox.el (test-org-export/activate-smart-quotes): Fix test with unbalanced " and add new tests for unbalanced quotes. Reported-by: Juan Manuel Macías <maciaschain@posteo.net> Link: https://list.orgmode.org/orgmode/875xxfqdpt.fsf@posteo.net/
This commit is contained in:
parent
8507efa848
commit
33503445e6
50
lisp/ox.el
50
lisp/ox.el
|
@ -5942,6 +5942,56 @@ INFO is the current export state, as a plist."
|
||||||
(when current-status
|
(when current-status
|
||||||
(push (cons text (nreverse current-status)) full-status))))
|
(push (cons text (nreverse current-status)) full-status))))
|
||||||
info nil org-element-recursive-objects)
|
info nil org-element-recursive-objects)
|
||||||
|
;; When quotes are not balanced, treat them as apostrophes.
|
||||||
|
(setq full-status (nreverse full-status))
|
||||||
|
(let (primary-openings secondary-openings)
|
||||||
|
(dolist (substatus full-status)
|
||||||
|
(let ((status (cdr substatus)))
|
||||||
|
(while status
|
||||||
|
(pcase (car status)
|
||||||
|
(`apostrophe nil)
|
||||||
|
(`primary-opening
|
||||||
|
(push status primary-openings))
|
||||||
|
(`secondary-opening
|
||||||
|
(push status secondary-openings))
|
||||||
|
(`secondary-closing
|
||||||
|
(if secondary-openings
|
||||||
|
;; Remove matched opening.
|
||||||
|
(pop secondary-openings)
|
||||||
|
;; No matching openings for a given closing. Replace
|
||||||
|
;; it with apostrophe.
|
||||||
|
(setcar status 'apostrophe)))
|
||||||
|
(`primary-closing
|
||||||
|
(when secondary-openings
|
||||||
|
;; Some secondary opening quotes are not closed
|
||||||
|
;; within "...". Replace them all with apostrophes.
|
||||||
|
(dolist (opening secondary-openings)
|
||||||
|
(setcar opening 'apostrophe))
|
||||||
|
(setq secondary-openings nil))
|
||||||
|
(if primary-openings
|
||||||
|
;; Remove matched opening.
|
||||||
|
(pop primary-openings)
|
||||||
|
;; No matching openings for a given closing.
|
||||||
|
(error "This should no happen"))))
|
||||||
|
(setq status (cdr status)))))
|
||||||
|
(when primary-openings
|
||||||
|
;; Trailing unclosed "
|
||||||
|
(unless (= 1 (length primary-openings))
|
||||||
|
(error "This should not happen"))
|
||||||
|
;; Mark for not replacing.
|
||||||
|
(setcar (car primary-openings) nil)
|
||||||
|
;; Mark all the secondary openings and closings after
|
||||||
|
;; trailing unclosed " as apostrophes.
|
||||||
|
(let ((after-unbalanced-primary nil))
|
||||||
|
(dolist (substatus full-status)
|
||||||
|
(let ((status (cdr substatus)))
|
||||||
|
(while status
|
||||||
|
(when (eq status (car primary-openings))
|
||||||
|
(setq after-unbalanced-primary t))
|
||||||
|
(when after-unbalanced-primary
|
||||||
|
(when (memq (car status) '(secondary-opening secondary-closing))
|
||||||
|
(setcar status 'apostrophe)))
|
||||||
|
(setq status (cdr status))))))))
|
||||||
(puthash (cons parent (org-element-secondary-p s)) full-status cache)
|
(puthash (cons parent (org-element-secondary-p s)) full-status cache)
|
||||||
(cdr (assq s full-status))))))
|
(cdr (assq s full-status))))))
|
||||||
|
|
||||||
|
|
|
@ -4134,9 +4134,9 @@ This test does not cover listings and custom environments."
|
||||||
;; Opening quotes: at the beginning of a paragraph.
|
;; Opening quotes: at the beginning of a paragraph.
|
||||||
(should
|
(should
|
||||||
(equal
|
(equal
|
||||||
'("“begin")
|
'("“begin”")
|
||||||
(let ((org-export-default-language "en"))
|
(let ((org-export-default-language "en"))
|
||||||
(org-test-with-parsed-data "\"begin"
|
(org-test-with-parsed-data "\"begin\""
|
||||||
(org-element-map tree 'plain-text
|
(org-element-map tree 'plain-text
|
||||||
(lambda (s) (org-export-activate-smart-quotes s :html info))
|
(lambda (s) (org-export-activate-smart-quotes s :html info))
|
||||||
info)))))
|
info)))))
|
||||||
|
@ -4267,6 +4267,39 @@ This test does not cover listings and custom environments."
|
||||||
(org-test-with-parsed-data "*\"foo\"*"
|
(org-test-with-parsed-data "*\"foo\"*"
|
||||||
(org-element-map tree 'plain-text
|
(org-element-map tree 'plain-text
|
||||||
(lambda (s) (org-export-activate-smart-quotes s :html info))
|
(lambda (s) (org-export-activate-smart-quotes s :html info))
|
||||||
|
info nil nil t)))))
|
||||||
|
;; Unmatched quotes.
|
||||||
|
(should
|
||||||
|
(equal '("\\guillemotleft{}my friends' party and the students' papers\\guillemotright{} \\guillemotleft{}``mothers''\\guillemotright{}")
|
||||||
|
(let ((org-export-default-language "es"))
|
||||||
|
(org-test-with-parsed-data
|
||||||
|
"\"my friends' party and the students' papers\" \"'mothers'\""
|
||||||
|
(org-element-map tree 'plain-text
|
||||||
|
(lambda (s) (org-export-activate-smart-quotes s :latex info))
|
||||||
|
info nil nil t)))))
|
||||||
|
(should
|
||||||
|
(equal '("\"'mothers'")
|
||||||
|
(let ((org-export-default-language "es"))
|
||||||
|
(org-test-with-parsed-data
|
||||||
|
"\"'mothers'"
|
||||||
|
(org-element-map tree 'plain-text
|
||||||
|
(lambda (s) (org-export-activate-smart-quotes s :latex info))
|
||||||
|
info nil nil t)))))
|
||||||
|
(should
|
||||||
|
(equal '("\"'mothers " "end'")
|
||||||
|
(let ((org-export-default-language "es"))
|
||||||
|
(org-test-with-parsed-data
|
||||||
|
"\"'mothers =verbatim= end'"
|
||||||
|
(org-element-map tree 'plain-text
|
||||||
|
(lambda (s) (org-export-activate-smart-quotes s :latex info))
|
||||||
|
info nil nil t)))))
|
||||||
|
(should
|
||||||
|
(equal '("\\guillemotleft{}να 'ρθώ το βράδυ\\guillemotright{}")
|
||||||
|
(let ((org-export-default-language "el"))
|
||||||
|
(org-test-with-parsed-data
|
||||||
|
"\"να 'ρθώ το βράδυ\""
|
||||||
|
(org-element-map tree 'plain-text
|
||||||
|
(lambda (s) (org-export-activate-smart-quotes s :latex info))
|
||||||
info nil nil t))))))
|
info nil nil t))))))
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue