Forráskód Böngészése

ox: Support unnumbered headlines via property

* ox.el (org-export--collect-headline-numbering): Ignore unnumbered headline.
(org-export-get-headline-id,
org-export--collect-unnumbered-headline-id): New functions.
(org-export-numbered-headline-p): Further tests for unnumbered headline.
* ox-odt.el (org-odt-headline, org-odt-link,
org-odt-link--infer-description)
ox-md.el (org-md-headline, org-md-link),
ox-latex.el (org-latex-headline, org.latex-link),
ox-html.el (org-html-headline, org-html-link),
ox-ascii.el (org-ascii-link): Support unnumbered headlines.
* test-ox.el (test-org-export/org-export-get-headline-id): New test.
* OrgOdtStyles.xml: Add styles for unnumbered headings.
Rasmus 11 éve
szülő
commit
464cd965cb
9 módosított fájl, 188 hozzáadás és 85 törlés
  1. 19 0
      etc/styles/OrgOdtStyles.xml
  2. 1 1
      lisp/org.el
  3. 6 2
      lisp/ox-ascii.el
  4. 17 23
      lisp/ox-html.el
  5. 6 17
      lisp/ox-latex.el
  6. 14 10
      lisp/ox-md.el
  7. 22 24
      lisp/ox-odt.el
  8. 41 8
      lisp/ox.el
  9. 62 0
      testing/lisp/test-ox.el

+ 19 - 0
etc/styles/OrgOdtStyles.xml

@@ -109,33 +109,52 @@
   </style:style>
   </style:style>
   <style:style style:name="Heading_20_1" style:display-name="Heading 1" style:family="paragraph" style:parent-style-name="Heading" style:next-style-name="Text_20_body" style:default-outline-level="1" style:class="text">
   <style:style style:name="Heading_20_1" style:display-name="Heading 1" style:family="paragraph" style:parent-style-name="Heading" style:next-style-name="Text_20_body" style:default-outline-level="1" style:class="text">
    <style:text-properties fo:font-size="115%" fo:font-weight="bold" style:font-size-asian="115%" style:font-weight-asian="bold" style:font-size-complex="115%" style:font-weight-complex="bold"/>
    <style:text-properties fo:font-size="115%" fo:font-weight="bold" style:font-size-asian="115%" style:font-weight-asian="bold" style:font-size-complex="115%" style:font-weight-complex="bold"/>
+   <style:style style:name="Heading_20_1_unnumbered" style:family="paragraph" style:parent-style-name="Heading_20_1" style:list-style-name="">
   </style:style>
   </style:style>
   <style:style style:name="Heading_20_2" style:display-name="Heading 2" style:family="paragraph" style:parent-style-name="Heading" style:next-style-name="Text_20_body" style:default-outline-level="2" style:class="text">
   <style:style style:name="Heading_20_2" style:display-name="Heading 2" style:family="paragraph" style:parent-style-name="Heading" style:next-style-name="Text_20_body" style:default-outline-level="2" style:class="text">
    <style:text-properties fo:font-size="14pt" fo:font-style="italic" fo:font-weight="bold" style:font-size-asian="14pt" style:font-style-asian="italic" style:font-weight-asian="bold" style:font-size-complex="14pt" style:font-style-complex="italic" style:font-weight-complex="bold"/>
    <style:text-properties fo:font-size="14pt" fo:font-style="italic" fo:font-weight="bold" style:font-size-asian="14pt" style:font-style-asian="italic" style:font-weight-asian="bold" style:font-size-complex="14pt" style:font-style-complex="italic" style:font-weight-complex="bold"/>
+  </style:style>
+    <style:style style:name="Heading_20_2_unnumbered" style:family="paragraph" style:parent-style-name="Heading_20_2" style:list-style-name="">
   </style:style>
   </style:style>
   <style:style style:name="Heading_20_3" style:display-name="Heading 3" style:family="paragraph" style:parent-style-name="Heading" style:next-style-name="Text_20_body" style:default-outline-level="3" style:class="text">
   <style:style style:name="Heading_20_3" style:display-name="Heading 3" style:family="paragraph" style:parent-style-name="Heading" style:next-style-name="Text_20_body" style:default-outline-level="3" style:class="text">
    <style:text-properties fo:font-size="14pt" fo:font-weight="bold" style:font-size-asian="14pt" style:font-weight-asian="bold" style:font-size-complex="14pt" style:font-weight-complex="bold"/>
    <style:text-properties fo:font-size="14pt" fo:font-weight="bold" style:font-size-asian="14pt" style:font-weight-asian="bold" style:font-size-complex="14pt" style:font-weight-complex="bold"/>
+  </style:style>
+    <style:style style:name="Heading_20_3_unnumbered" style:family="paragraph" style:parent-style-name="Heading_20_3" style:list-style-name="">
   </style:style>
   </style:style>
   <style:style style:name="Heading_20_4" style:display-name="Heading 4" style:family="paragraph" style:parent-style-name="Heading" style:next-style-name="Text_20_body" style:default-outline-level="4" style:class="text">
   <style:style style:name="Heading_20_4" style:display-name="Heading 4" style:family="paragraph" style:parent-style-name="Heading" style:next-style-name="Text_20_body" style:default-outline-level="4" style:class="text">
    <style:text-properties fo:font-size="85%" fo:font-style="italic" fo:font-weight="bold" style:font-size-asian="85%" style:font-style-asian="italic" style:font-weight-asian="bold" style:font-size-complex="85%" style:font-style-complex="italic" style:font-weight-complex="bold"/>
    <style:text-properties fo:font-size="85%" fo:font-style="italic" fo:font-weight="bold" style:font-size-asian="85%" style:font-style-asian="italic" style:font-weight-asian="bold" style:font-size-complex="85%" style:font-style-complex="italic" style:font-weight-complex="bold"/>
+  </style:style>
+    <style:style style:name="Heading_20_4_unnumbered" style:family="paragraph" style:parent-style-name="Heading_20_4" style:list-style-name="">
   </style:style>
   </style:style>
   <style:style style:name="Heading_20_5" style:display-name="Heading 5" style:family="paragraph" style:parent-style-name="Heading" style:next-style-name="Text_20_body" style:default-outline-level="5" style:class="text">
   <style:style style:name="Heading_20_5" style:display-name="Heading 5" style:family="paragraph" style:parent-style-name="Heading" style:next-style-name="Text_20_body" style:default-outline-level="5" style:class="text">
    <style:text-properties fo:font-size="85%" fo:font-weight="bold" style:font-size-asian="85%" style:font-weight-asian="bold" style:font-size-complex="85%" style:font-weight-complex="bold"/>
    <style:text-properties fo:font-size="85%" fo:font-weight="bold" style:font-size-asian="85%" style:font-weight-asian="bold" style:font-size-complex="85%" style:font-weight-complex="bold"/>
+  </style:style>
+    <style:style style:name="Heading_20_5_unnumbered" style:family="paragraph" style:parent-style-name="Heading_20_5" style:list-style-name="">
   </style:style>
   </style:style>
   <style:style style:name="Heading_20_6" style:display-name="Heading 6" style:family="paragraph" style:parent-style-name="Heading" style:next-style-name="Text_20_body" style:default-outline-level="6" style:class="text">
   <style:style style:name="Heading_20_6" style:display-name="Heading 6" style:family="paragraph" style:parent-style-name="Heading" style:next-style-name="Text_20_body" style:default-outline-level="6" style:class="text">
    <style:text-properties fo:font-size="75%" fo:font-weight="bold" style:font-size-asian="75%" style:font-weight-asian="bold" style:font-size-complex="75%" style:font-weight-complex="bold"/>
    <style:text-properties fo:font-size="75%" fo:font-weight="bold" style:font-size-asian="75%" style:font-weight-asian="bold" style:font-size-complex="75%" style:font-weight-complex="bold"/>
+  </style:style>
+    <style:style style:name="Heading_20_6_unnumbered" style:family="paragraph" style:parent-style-name="Heading_20_6" style:list-style-name="">
   </style:style>
   </style:style>
   <style:style style:name="Heading_20_7" style:display-name="Heading 7" style:family="paragraph" style:parent-style-name="Heading" style:next-style-name="Text_20_body" style:default-outline-level="7" style:class="text">
   <style:style style:name="Heading_20_7" style:display-name="Heading 7" style:family="paragraph" style:parent-style-name="Heading" style:next-style-name="Text_20_body" style:default-outline-level="7" style:class="text">
    <style:text-properties fo:font-size="75%" fo:font-weight="bold" style:font-size-asian="75%" style:font-weight-asian="bold" style:font-size-complex="75%" style:font-weight-complex="bold"/>
    <style:text-properties fo:font-size="75%" fo:font-weight="bold" style:font-size-asian="75%" style:font-weight-asian="bold" style:font-size-complex="75%" style:font-weight-complex="bold"/>
+  </style:style>
+    <style:style style:name="Heading_20_7_unnumbered" style:family="paragraph" style:parent-style-name="Heading_20_7" style:list-style-name="">
   </style:style>
   </style:style>
   <style:style style:name="Heading_20_8" style:display-name="Heading 8" style:family="paragraph" style:parent-style-name="Heading" style:next-style-name="Text_20_body" style:default-outline-level="8" style:class="text">
   <style:style style:name="Heading_20_8" style:display-name="Heading 8" style:family="paragraph" style:parent-style-name="Heading" style:next-style-name="Text_20_body" style:default-outline-level="8" style:class="text">
    <style:text-properties fo:font-size="75%" fo:font-weight="bold" style:font-size-asian="75%" style:font-weight-asian="bold" style:font-size-complex="75%" style:font-weight-complex="bold"/>
    <style:text-properties fo:font-size="75%" fo:font-weight="bold" style:font-size-asian="75%" style:font-weight-asian="bold" style:font-size-complex="75%" style:font-weight-complex="bold"/>
+  </style:style>
+    <style:style style:name="Heading_20_8_unnumbered" style:family="paragraph" style:parent-style-name="Heading_20_8" style:list-style-name="">
   </style:style>
   </style:style>
   <style:style style:name="Heading_20_9" style:display-name="Heading 9" style:family="paragraph" style:parent-style-name="Heading" style:next-style-name="Text_20_body" style:default-outline-level="9" style:class="text">
   <style:style style:name="Heading_20_9" style:display-name="Heading 9" style:family="paragraph" style:parent-style-name="Heading" style:next-style-name="Text_20_body" style:default-outline-level="9" style:class="text">
    <style:text-properties fo:font-size="75%" fo:font-weight="bold" style:font-size-asian="75%" style:font-weight-asian="bold" style:font-size-complex="75%" style:font-weight-complex="bold"/>
    <style:text-properties fo:font-size="75%" fo:font-weight="bold" style:font-size-asian="75%" style:font-weight-asian="bold" style:font-size-complex="75%" style:font-weight-complex="bold"/>
   </style:style>
   </style:style>
+    <style:style style:name="Heading_20_9_unnumbered" style:family="paragraph" style:parent-style-name="Heading_20_9" style:list-style-name="">
+    </style:style>
   <style:style style:name="Heading_20_10" style:display-name="Heading 10" style:family="paragraph" style:parent-style-name="Heading" style:next-style-name="Text_20_body" style:default-outline-level="10" style:class="text">
   <style:style style:name="Heading_20_10" style:display-name="Heading 10" style:family="paragraph" style:parent-style-name="Heading" style:next-style-name="Text_20_body" style:default-outline-level="10" style:class="text">
    <style:text-properties fo:font-size="75%" fo:font-weight="bold" style:font-size-asian="75%" style:font-weight-asian="bold" style:font-size-complex="75%" style:font-weight-complex="bold"/>
    <style:text-properties fo:font-size="75%" fo:font-weight="bold" style:font-size-asian="75%" style:font-weight-asian="bold" style:font-size-complex="75%" style:font-weight-complex="bold"/>
+  </style:style>
+    <style:style style:name="Heading_20_10_unnumbered" style:family="paragraph" style:parent-style-name="Heading_20_10" style:list-style-name="">
   </style:style>
   </style:style>
   <style:style style:name="Heading_20_1.title" style:display-name="Heading 1.title" style:family="paragraph" style:parent-style-name="Heading_20_1">
   <style:style style:name="Heading_20_1.title" style:display-name="Heading 1.title" style:family="paragraph" style:parent-style-name="Heading_20_1">
    <style:paragraph-properties fo:text-align="center" style:justify-single-word="false"/>
    <style:paragraph-properties fo:text-align="center" style:justify-single-word="false"/>

+ 1 - 1
lisp/org.el

@@ -15340,7 +15340,7 @@ but in some other way.")
     "LOCATION" "LOGGING" "COLUMNS" "VISIBILITY"
     "LOCATION" "LOGGING" "COLUMNS" "VISIBILITY"
     "TABLE_EXPORT_FORMAT" "TABLE_EXPORT_FILE"
     "TABLE_EXPORT_FORMAT" "TABLE_EXPORT_FILE"
     "EXPORT_OPTIONS" "EXPORT_TEXT" "EXPORT_FILE_NAME"
     "EXPORT_OPTIONS" "EXPORT_TEXT" "EXPORT_FILE_NAME"
-    "EXPORT_TITLE" "EXPORT_AUTHOR" "EXPORT_DATE"
+    "EXPORT_TITLE" "EXPORT_AUTHOR" "EXPORT_DATE" "UNNUMBERED"
     "ORDERED" "NOBLOCKING" "COOKIE_DATA" "LOG_INTO_DRAWER" "REPEAT_TO_STATE"
     "ORDERED" "NOBLOCKING" "COOKIE_DATA" "LOG_INTO_DRAWER" "REPEAT_TO_STATE"
     "CLOCK_MODELINE_TOTAL" "STYLE" "HTML_CONTAINER_CLASS")
     "CLOCK_MODELINE_TOTAL" "STYLE" "HTML_CONTAINER_CLASS")
   "Some properties that are used by Org-mode for various purposes.
   "Some properties that are used by Org-mode for various purposes.

+ 6 - 2
lisp/ox-ascii.el

@@ -1530,9 +1530,13 @@ INFO is a plist holding contextual information."
 	    (let ((number
 	    (let ((number
 		   (org-export-get-ordinal
 		   (org-export-get-ordinal
 		    destination info nil 'org-ascii--has-caption-p)))
 		    destination info nil 'org-ascii--has-caption-p)))
-	      (when number
+	      (if number
 		(if (atom number) (number-to-string number)
 		(if (atom number) (number-to-string number)
-		  (mapconcat 'number-to-string number "."))))))))
+		  (mapconcat #'number-to-string number "."))
+		;; Unnumbered headline.
+		(when (eq 'headline (org-element-type destination))
+		  (format "[%s]" (org-export-data
+				  (org-element-property :title destination) info)))))))))
      (t
      (t
       (if (not (org-string-nw-p desc)) (format "[%s]" raw-link)
       (if (not (org-string-nw-p desc)) (format "[%s]" raw-link)
 	(concat (format "[%s]" desc)
 	(concat (format "[%s]" desc)

+ 17 - 23
lisp/ox-html.el

@@ -2096,8 +2096,7 @@ INFO is a plist used as a communication channel."
 	    ;; Label.
 	    ;; Label.
 	    (org-export-solidify-link-text
 	    (org-export-solidify-link-text
 	     (or (org-element-property :CUSTOM_ID headline)
 	     (or (org-element-property :CUSTOM_ID headline)
-		 (concat "sec-"
-			 (mapconcat #'number-to-string headline-number "-"))))
+		 (org-export-get-headline-id headline info)))
 	    ;; Body.
 	    ;; Body.
 	    (concat
 	    (concat
 	     (and (not (org-export-low-level-p headline info))
 	     (and (not (org-export-low-level-p headline info))
@@ -2321,7 +2320,8 @@ holding contextual information."
   (unless (org-element-property :footnote-section-p headline)
   (unless (org-element-property :footnote-section-p headline)
     (let* ((numberedp (org-export-numbered-headline-p headline info))
     (let* ((numberedp (org-export-numbered-headline-p headline info))
            (numbers (org-export-get-headline-number headline info))
            (numbers (org-export-get-headline-number headline info))
-           (section-number (mapconcat #'number-to-string numbers "-"))
+           (section-number (and numbers
+				(mapconcat #'number-to-string numbers "-")))
            (level (+ (org-export-get-relative-level headline info)
            (level (+ (org-export-get-relative-level headline info)
                      (1- (plist-get info :html-toplevel-hlevel))))
                      (1- (plist-get info :html-toplevel-hlevel))))
            (todo (and (plist-get info :with-todo-keywords)
            (todo (and (plist-get info :with-todo-keywords)
@@ -2338,7 +2338,7 @@ holding contextual information."
            (contents (or contents ""))
            (contents (or contents ""))
            (ids (delq nil
            (ids (delq nil
                       (list (org-element-property :CUSTOM_ID headline)
                       (list (org-element-property :CUSTOM_ID headline)
-                            (concat "sec-" section-number)
+                            (org-export-get-headline-id headline info)
                             (org-element-property :ID headline))))
                             (org-element-property :ID headline))))
            (preferred-id (car ids))
            (preferred-id (car ids))
            (extra-ids (mapconcat
            (extra-ids (mapconcat
@@ -2369,7 +2369,7 @@ holding contextual information."
                   (org-html--container headline info)
                   (org-html--container headline info)
                   (format "outline-container-%s"
                   (format "outline-container-%s"
                           (or (org-element-property :CUSTOM_ID headline)
                           (or (org-element-property :CUSTOM_ID headline)
-                              (concat "sec-" section-number)))
+                              (org-export-get-headline-id headline info)))
                   (concat (format "outline-%d" level)
                   (concat (format "outline-%d" level)
                           (and extra-class " ")
                           (and extra-class " ")
                           extra-class)
                           extra-class)
@@ -2808,20 +2808,11 @@ INFO is a plist holding contextual information.  See
 	  ;; Link points to a headline.
 	  ;; Link points to a headline.
 	  (headline
 	  (headline
 	   (let ((href
 	   (let ((href
-		  ;; What href to use?
-		  (cond
-		   ;; Case 1: Headline is linked via it's CUSTOM_ID
-		   ;; property.  Use CUSTOM_ID.
-		   ((string= type "custom-id")
-		    (org-element-property :CUSTOM_ID destination))
-		   ;; Case 2: Headline is linked via it's ID property
-		   ;; or through other means.  Use the default href.
-		   ((member type '("id" "fuzzy"))
-		    (format "sec-%s"
-			    (mapconcat 'number-to-string
-				       (org-export-get-headline-number
-					destination info) "-")))
-		   (t (error "Shouldn't reach here"))))
+		  ;; Headline linked via CUSTOM_ID.
+		  (or (and (string= type "custom-id")
+			   (org-element-property :CUSTOM_ID destination))
+		      (org-export-get-headline-id destination info)
+		      (t (error "Shouldn't reach here"))))
 		 ;; What description to use?
 		 ;; What description to use?
 		 (desc
 		 (desc
 		  ;; Case 1: Headline is numbered and LINK has no
 		  ;; Case 1: Headline is numbered and LINK has no
@@ -3073,13 +3064,16 @@ holding contextual information."
       (let* ((class-num (+ (org-export-get-relative-level parent info)
       (let* ((class-num (+ (org-export-get-relative-level parent info)
 			   (1- (plist-get info :html-toplevel-hlevel))))
 			   (1- (plist-get info :html-toplevel-hlevel))))
 	     (section-number
 	     (section-number
-	      (mapconcat
-	       'number-to-string
-	       (org-export-get-headline-number parent info) "-")))
+	      (and (org-export-numbered-headline-p parent info)
+		   (mapconcat
+		    #'number-to-string
+		    (org-export-get-headline-number parent info) "-"))))
         ;; Build return value.
         ;; Build return value.
 	(format "<div class=\"outline-text-%d\" id=\"text-%s\">\n%s</div>"
 	(format "<div class=\"outline-text-%d\" id=\"text-%s\">\n%s</div>"
 		class-num
 		class-num
-		(or (org-element-property :CUSTOM_ID parent) section-number)
+		(or (org-element-property :CUSTOM_ID parent)
+		    section-number
+		    (org-export-get-headline-id parent info))
 		contents)))))
 		contents)))))
 
 
 ;;;; Radio Target
 ;;;; Radio Target

+ 6 - 17
lisp/ox-latex.el

@@ -1477,15 +1477,10 @@ holding contextual information."
 			       todo todo-type priority text tags info))
 			       todo todo-type priority text tags info))
 	   ;; Associate \label to the headline for internal links.
 	   ;; Associate \label to the headline for internal links.
 	   (headline-label
 	   (headline-label
-	    (let ((custom-label
-		   (and (plist-get info :latex-custom-id-labels)
-			(org-element-property :CUSTOM_ID headline))))
-	      (if custom-label (format "\\label{%s}\n" custom-label)
-		(format "\\label{sec-%s}\n"
-			(mapconcat
-			 #'number-to-string
-			 (org-export-get-headline-number headline info)
-			 "-")))))
+	    (format "\\label{%s}\n"
+		    (or (and (plist-get info :latex-custom-id-labels)
+			     (org-element-property :CUSTOM_ID headline))
+			(org-export-get-headline-id headline info))))
 	   (pre-blanks
 	   (pre-blanks
 	    (make-string (org-element-property :pre-blank headline) 10)))
 	    (make-string (org-element-property :pre-blank headline) 10)))
       (if (or (not section-fmt) (org-export-low-level-p headline info))
       (if (or (not section-fmt) (org-export-low-level-p headline info))
@@ -1975,14 +1970,8 @@ INFO is a plist holding contextual information.  See
 	   (let* ((custom-label
 	   (let* ((custom-label
 		   (and (plist-get info :latex-custom-id-labels)
 		   (and (plist-get info :latex-custom-id-labels)
 			(org-element-property :CUSTOM_ID destination)))
 			(org-element-property :CUSTOM_ID destination)))
-		  (label
-		   (or
-		    custom-label
-		    (format "sec-%s"
-			    (mapconcat
-			     #'number-to-string
-			     (org-export-get-headline-number destination info)
-			     "-")))))
+		  (label (or custom-label
+			     (org-export-get-headline-id destination info))))
 	     (if (and (not desc)
 	     (if (and (not desc)
 		      (org-export-numbered-headline-p destination info))
 		      (org-export-numbered-headline-p destination info))
 		 (format "\\ref{%s}" label)
 		 (format "\\ref{%s}" label)

+ 14 - 10
lisp/ox-md.el

@@ -204,10 +204,7 @@ a communication channel."
 	    (when (plist-get info :with-toc)
 	    (when (plist-get info :with-toc)
 	      (org-html--anchor
 	      (org-html--anchor
 	       (or (org-element-property :CUSTOM_ID headline)
 	       (or (org-element-property :CUSTOM_ID headline)
-		   (concat "sec-"
-			   (mapconcat 'number-to-string
-				      (org-export-get-headline-number
-				       headline info) "-")))
+		   (org-export-get-headline-id headline info))
 	       nil nil info)))
 	       nil nil info)))
 	   ;; Headline text without tags.
 	   ;; Headline text without tags.
 	   (heading (concat todo priority title))
 	   (heading (concat todo priority title))
@@ -330,10 +327,13 @@ a communication channel."
 	   (and contents (concat contents " "))
 	   (and contents (concat contents " "))
 	   (format "(%s)"
 	   (format "(%s)"
 		   (format (org-export-translate "See section %s" :html info)
 		   (format (org-export-translate "See section %s" :html info)
-			   (mapconcat 'number-to-string
-				      (org-export-get-headline-number
-				       destination info)
-				      ".")))))))
+			   (if (org-export-numbered-headline-p destination info)
+			       (mapconcat #'number-to-string
+					  (org-export-get-headline-number
+					   destination info)
+					  ".")
+			     (org-export-data
+			      (org-element-property :title destination) info))))))))
      ((org-export-inline-image-p link org-html-inline-image-rules)
      ((org-export-inline-image-p link org-html-inline-image-rules)
       (let ((path (let ((raw-path (org-element-property :path link)))
       (let ((path (let ((raw-path (org-element-property :path link)))
 		    (if (not (file-name-absolute-p raw-path)) raw-path
 		    (if (not (file-name-absolute-p raw-path)) raw-path
@@ -354,9 +354,13 @@ a communication channel."
 	(if (org-string-nw-p contents) contents
 	(if (org-string-nw-p contents) contents
 	  (when destination
 	  (when destination
 	    (let ((number (org-export-get-ordinal destination info)))
 	    (let ((number (org-export-get-ordinal destination info)))
-	      (when number
+	      (if number
 		(if (atom number) (number-to-string number)
 		(if (atom number) (number-to-string number)
-		  (mapconcat 'number-to-string number "."))))))))
+		  (mapconcat #'number-to-string number "."))
+		;; Unnumbered headline.
+		(and (eq 'headline (org-element-type destination))
+		  ;; BUG: shouldn't headlines have a form like [ref](name) in md?
+		  (org-export-data (org-element-property :title headline) info))))))))
      ;; Link type is handled by a special function.
      ;; Link type is handled by a special function.
      ((let ((protocol (nth 2 (assoc type org-link-protocols))))
      ((let ((protocol (nth 2 (assoc type org-link-protocols))))
 	(and (functionp protocol)
 	(and (functionp protocol)

+ 22 - 24
lisp/ox-odt.el

@@ -1122,7 +1122,7 @@ See `org-odt--build-date-styles' for implementation details."
   (setq text
   (setq text
 	(concat
 	(concat
 	 ;; Section number.
 	 ;; Section number.
-	 (when section-number (concat section-number ". "))
+	 (and section-number (concat section-number ". "))
 	 ;; Todo.
 	 ;; Todo.
 	 (when todo
 	 (when todo
 	   (let ((style (if (member todo org-done-keywords)
 	   (let ((style (if (member todo org-done-keywords)
@@ -1789,8 +1789,7 @@ INFO is a plist holding contextual information."
 		(org-element-property :title headline) backend info))
 		(org-element-property :title headline) backend info))
 	 (tags (and (plist-get info :with-tags)
 	 (tags (and (plist-get info :with-tags)
 		    (org-export-get-tags headline info)))
 		    (org-export-get-tags headline info)))
-	 (headline-label (concat "sec-" (mapconcat 'number-to-string
-						   headline-number "-")))
+	 (headline-label (org-export-get-headline-id headline info))
 	 (format-function
 	 (format-function
 	  (if (functionp format-function) format-function
 	  (if (functionp format-function) format-function
 	    (function*
 	    (function*
@@ -1815,10 +1814,9 @@ holding contextual information."
 	   (full-text (org-odt-format-headline--wrap headline nil info))
 	   (full-text (org-odt-format-headline--wrap headline nil info))
 	   ;; Get level relative to current parsed data.
 	   ;; Get level relative to current parsed data.
 	   (level (org-export-get-relative-level headline info))
 	   (level (org-export-get-relative-level headline info))
+	   (numbered (org-export-numbered-headline-p headline info))
 	   ;; Get canonical label for the headline.
 	   ;; Get canonical label for the headline.
-	   (id (concat "sec-" (mapconcat 'number-to-string
-					 (org-export-get-headline-number
-					  headline info) "-")))
+	   (id (org-export-get-headline-id headline info))
 	   ;; Get user-specified labels for the headline.
 	   ;; Get user-specified labels for the headline.
 	   (extra-ids (list (org-element-property :CUSTOM_ID headline)
 	   (extra-ids (list (org-element-property :CUSTOM_ID headline)
 			    (org-element-property :ID headline)))
 			    (org-element-property :ID headline)))
@@ -1842,8 +1840,7 @@ holding contextual information."
 	 (and (org-export-first-sibling-p headline info)
 	 (and (org-export-first-sibling-p headline info)
 	      (format "\n<text:list text:style-name=\"%s\" %s>"
 	      (format "\n<text:list text:style-name=\"%s\" %s>"
 		      ;; Choose style based on list type.
 		      ;; Choose style based on list type.
-		      (if (org-export-numbered-headline-p headline info)
-			  "OrgNumberedList" "OrgBulletedList")
+		      (if numbered "OrgNumberedList" "OrgBulletedList")
 		      ;; If top-level list, re-start numbering.  Otherwise,
 		      ;; If top-level list, re-start numbering.  Otherwise,
 		      ;; continue numbering.
 		      ;; continue numbering.
 		      (format "text:continue-numbering=\"%s\""
 		      (format "text:continue-numbering=\"%s\""
@@ -1870,9 +1867,11 @@ holding contextual information."
        (t
        (t
 	(concat
 	(concat
 	 (format
 	 (format
-	  "\n<text:h text:style-name=\"%s\" text:outline-level=\"%s\">%s</text:h>"
-	  (format "Heading_20_%s" level)
+	  "\n<text:h text:style-name=\"%s\" text:outline-level=\"%s\" text:is-list-header=\"%s\">%s</text:h>"
+	  (format "Heading_20_%s%s"
+		  level (if numbered "" "_unnumbered"))
 	  level
 	  level
+	  (if numbered "false" "true")
 	  (concat extra-targets anchored-title))
 	  (concat extra-targets anchored-title))
 	 contents))))))
 	 contents))))))
 
 
@@ -2643,10 +2642,7 @@ Return nil, otherwise."
   (let* ((genealogy (org-export-get-genealogy destination))
   (let* ((genealogy (org-export-get-genealogy destination))
 	 (data (reverse genealogy))
 	 (data (reverse genealogy))
 	 (label (case (org-element-type destination)
 	 (label (case (org-element-type destination)
-		  (headline
-		   (format "sec-%s" (mapconcat 'number-to-string
-					       (org-export-get-headline-number
-						destination info) "-")))
+		  (headline (org-export-get-headline-id destination info))
 		  (target
 		  (target
 		   (org-element-property :value destination))
 		   (org-element-property :value destination))
 		  (t (error "FIXME: Resolve %S" destination)))))
 		  (t (error "FIXME: Resolve %S" destination)))))
@@ -2692,11 +2688,15 @@ Return nil, otherwise."
 			      item-numbers "")))))
 			      item-numbers "")))))
      ;; Case 2: Locate a regular and numbered headline in the
      ;; Case 2: Locate a regular and numbered headline in the
      ;; hierarchy.  Display its section number.
      ;; hierarchy.  Display its section number.
-     (let ((headline (loop for el in (cons destination genealogy)
-			   when (and (eq (org-element-type el) 'headline)
-				     (not (org-export-low-level-p el info))
-				     (org-export-numbered-headline-p el info))
-			   return el)))
+     (let ((headline
+	    (and
+	     ;; Test if destination is a numbered headline.
+	     (org-export-numbered-headline-p destination info)
+	     (loop for el in (cons destination genealogy)
+		   when (and (eq (org-element-type el) 'headline)
+			     (not (org-export-low-level-p el info))
+			     (org-export-numbered-headline-p el info))
+		   return el))))
        ;; We found one.
        ;; We found one.
        (when headline
        (when headline
 	 (format "<text:bookmark-ref text:reference-format=\"chapter\" text:ref-name=\"OrgXref.%s\">%s</text:bookmark-ref>"
 	 (format "<text:bookmark-ref text:reference-format=\"chapter\" text:ref-name=\"OrgXref.%s\">%s</text:bookmark-ref>"
@@ -2776,11 +2776,9 @@ INFO is a plist holding contextual information.  See
 	   ;; If there's a description, create a hyperlink.
 	   ;; If there's a description, create a hyperlink.
 	   ;; Otherwise, try to provide a meaningful description.
 	   ;; Otherwise, try to provide a meaningful description.
 	   (if (not desc) (org-odt-link--infer-description destination info)
 	   (if (not desc) (org-odt-link--infer-description destination info)
-	     (let* ((headline-no
-		     (org-export-get-headline-number destination info))
-		    (label
-		     (format "sec-%s"
-			     (mapconcat 'number-to-string headline-no "-"))))
+	     (let ((label (or (and (string= type "custom-id")
+				   (org-element-property :CUSTOM_ID destination))
+			      (org-export-get-headline-id destination info))))
 	       (format
 	       (format
 		"<text:a xlink:type=\"simple\" xlink:href=\"#%s\">%s</text:a>"
 		"<text:a xlink:type=\"simple\" xlink:href=\"#%s\">%s</text:a>"
 		label desc))))
 		label desc))))

+ 41 - 8
lisp/ox.el

@@ -1974,6 +1974,7 @@ Return updated plist."
   ;; properties.
   ;; properties.
   (nconc
   (nconc
    `(:headline-numbering ,(org-export--collect-headline-numbering data info)
    `(:headline-numbering ,(org-export--collect-headline-numbering data info)
+     :unnumbered-headline-id ,(org-export--collect-unnumbered-headline-id data info)
      :exported-data ,(make-hash-table :test 'eq :size 4001))
      :exported-data ,(make-hash-table :test 'eq :size 4001))
    info))
    info))
 
 
@@ -1996,7 +1997,7 @@ OPTIONS is a plist holding export options."
       (if (= min-level 10000) 1 min-level))))
       (if (= min-level 10000) 1 min-level))))
 
 
 (defun org-export--collect-headline-numbering (data options)
 (defun org-export--collect-headline-numbering (data options)
-  "Return numbering of all exportable headlines in a parse tree.
+  "Return numbering of all exportable, numbered headlines in a parse tree.
 
 
 DATA is the parse tree.  OPTIONS is the plist holding export
 DATA is the parse tree.  OPTIONS is the plist holding export
 options.
 options.
@@ -2007,7 +2008,8 @@ for a footnotes section."
   (let ((numbering (make-vector org-export-max-depth 0)))
   (let ((numbering (make-vector org-export-max-depth 0)))
     (org-element-map data 'headline
     (org-element-map data 'headline
       (lambda (headline)
       (lambda (headline)
-	(unless (org-element-property :footnote-section-p headline)
+	(when (and (org-export-numbered-headline-p headline options)
+		   (not (org-element-property :footnote-section-p headline)))
 	  (let ((relative-level
 	  (let ((relative-level
 		 (1- (org-export-get-relative-level headline options))))
 		 (1- (org-export-get-relative-level headline options))))
 	    (cons
 	    (cons
@@ -2019,6 +2021,17 @@ for a footnotes section."
 		   when (> idx relative-level) do (aset numbering idx 0))))))
 		   when (> idx relative-level) do (aset numbering idx 0))))))
       options)))
       options)))
 
 
+(defun org-export--collect-unnumbered-headline-id (data options)
+  "Return numbering of all exportable, unnumbered headlines.
+DATA is the parse tree.  OPTIONS is the plist holding export
+options.  Unnumbered headlines are numbered as a function of
+occurrence."
+  (let ((num 0))
+    (org-element-map data 'headline
+	(lambda (headline)
+	  (unless (org-export-numbered-headline-p headline options)
+	    (list headline (incf num)))))))
+
 (defun org-export--populate-ignore-list (data options)
 (defun org-export--populate-ignore-list (data options)
   "Return list of elements and objects to ignore during export.
   "Return list of elements and objects to ignore during export.
 DATA is the parse tree to traverse.  OPTIONS is the plist holding
 DATA is the parse tree to traverse.  OPTIONS is the plist holding
@@ -3873,7 +3886,12 @@ INFO is the plist used as a communication channel."
 ;;
 ;;
 ;; `org-export-get-headline-number' returns the section number of an
 ;; `org-export-get-headline-number' returns the section number of an
 ;; headline, while `org-export-number-to-roman' allows to convert it
 ;; headline, while `org-export-number-to-roman' allows to convert it
-;; to roman numbers.
+;; to roman numbers.  With an optional argument,
+;; `org-export-get-headline-number' returns a number to unnumbered
+;; headlines (used for internal id).
+;;
+;; `org-export-get-headline-id' returns the unique internal id of a
+;; headline.
 ;;
 ;;
 ;; `org-export-low-level-p', `org-export-first-sibling-p' and
 ;; `org-export-low-level-p', `org-export-first-sibling-p' and
 ;; `org-export-last-sibling-p' are three useful predicates when it
 ;; `org-export-last-sibling-p' are three useful predicates when it
@@ -3908,17 +3926,32 @@ and the last level being considered as high enough, or nil."
       (let ((level (org-export-get-relative-level headline info)))
       (let ((level (org-export-get-relative-level headline info)))
         (and (> level limit) (- level limit))))))
         (and (> level limit) (- level limit))))))
 
 
+(defun org-export-get-headline-id (headline info)
+  "Return a unique ID for HEADLINE.
+INFO is a plist holding contextual information."
+  (let ((numbered (org-export-numbered-headline-p headline info)))
+    (concat
+     (if numbered "sec-" "unnumbered-")
+     (mapconcat #'number-to-string
+		(if numbered
+		    (org-export-get-headline-number headline info)
+		  (cdr (assq headline (plist-get info :unnumbered-headline-id)))) "-"))))
+
 (defun org-export-get-headline-number (headline info)
 (defun org-export-get-headline-number (headline info)
-  "Return HEADLINE numbering as a list of numbers.
+  "Return numbered HEADLINE numbering as a list of numbers.
 INFO is a plist holding contextual information."
 INFO is a plist holding contextual information."
-  (cdr (assoc headline (plist-get info :headline-numbering))))
+  (and (org-export-numbered-headline-p headline info)
+       (cdr (assq headline (plist-get info :headline-numbering)))))
 
 
 (defun org-export-numbered-headline-p (headline info)
 (defun org-export-numbered-headline-p (headline info)
   "Return a non-nil value if HEADLINE element should be numbered.
   "Return a non-nil value if HEADLINE element should be numbered.
 INFO is a plist used as a communication channel."
 INFO is a plist used as a communication channel."
-  (let ((sec-num (plist-get info :section-numbers))
-	(level (org-export-get-relative-level headline info)))
-    (if (wholenump sec-num) (<= level sec-num) sec-num)))
+  (unless (org-some
+	   (lambda (head) (org-not-nil (org-element-property :UNNUMBERED head)))
+	   (cons headline (org-export-get-genealogy headline)))
+    (let ((sec-num (plist-get info :section-numbers))
+	  (level (org-export-get-relative-level headline info)))
+      (if (wholenump sec-num) (<= level sec-num) sec-num))))
 
 
 (defun org-export-number-to-roman (n)
 (defun org-export-number-to-roman (n)
   "Convert integer N into a roman numeral."
   "Convert integer N into a roman numeral."

+ 62 - 0
testing/lisp/test-ox.el

@@ -1633,6 +1633,68 @@ Paragraph[fn:1]"
        (lambda (h) (org-export-numbered-headline-p h info))
        (lambda (h) (org-export-numbered-headline-p h info))
        (plist-put info :section-numbers t)))))
        (plist-put info :section-numbers t)))))
 
 
+(ert-deftest test-org-export/org-export-get-headline-id ()
+  "Test `org-export-get-headline-id' specifications."
+  ;; Numbered headlines have IDs akin to "sec-N".
+  (should
+   (equal "sec-1"
+	  (org-test-with-parsed-data "* H"
+	    (org-export-get-headline-id
+	     (org-element-map tree 'headline #'identity info t)
+	     info))))
+  ;; The ID of numbered headlines reflect the hierarchy.
+  (should
+   (equal "sec-1-1"
+	  (org-test-with-parsed-data "* H1\n** H2"
+	    (org-export-get-headline-id
+	     (org-element-map tree 'headline
+	       (lambda (h)
+		 (and (equal "H2" (org-element-property :raw-value h)) h))
+	       info t)
+	     info))))
+  ;; Unnumbered headlines have IDs akin to "unnumbered-N".
+  (should
+   (equal "unnumbered-1"
+	  (org-test-with-parsed-data
+	      "* H\n:PROPERTIES:\n:UNNUMBERED: t\n:END:"
+	    (org-export-get-headline-id
+	     (org-element-map tree 'headline #'identity info t)
+	     info))))
+  ;; The ID of Unnumbered headlines do not reflect the hierarchy.
+  (should
+   (equal "unnumbered-2"
+	  (org-test-with-parsed-data
+	      "* H1\n:PROPERTIES:\n:UNNUMBERED: t\n:END:\n** H2"
+	    (org-export-get-headline-id
+	     (org-element-map tree 'headline
+	       (lambda (h)
+		 (and (equal "H2" (org-element-property :raw-value h)) h))
+	       info t)
+	     info))))
+  ;; When #+OPTIONS: num:nil all headlines are unnumbered.
+  (should
+   (equal "unnumbered-1"
+	  (org-test-with-parsed-data "* H\n#+OPTIONS: num:nil"
+	    (org-export-get-headline-id
+	     (org-element-map tree 'headline 'identity info t)
+	     info))))
+  ;; UNNUMBERED ignores inheritance.  Any non-nil value among
+  ;; ancestors disables numbering.
+  (should
+   (org-test-with-parsed-data
+       "* H
+:PROPERTIES:
+:UNNUMBERED: t
+:END:
+** H2
+:PROPERTIES:
+:UNNUMBERED: nil
+:END:
+*** H3"
+     (org-every
+      (lambda (h) (not (org-export-numbered-headline-p h info)))
+      (org-element-map tree 'headline #'identity info)))))
+
 (ert-deftest test-org-export/number-to-roman ()
 (ert-deftest test-org-export/number-to-roman ()
   "Test `org-export-number-to-roman' specifications."
   "Test `org-export-number-to-roman' specifications."
   ;; If number is negative, return it as a string.
   ;; If number is negative, return it as a string.