Hi,

after getting an encouraging reply to my initial proposal[1], I went
forward and finished the patch to include the BBCode exporter in Org.
The patch applies to current master and includes a suite of tests.

Please review and/or apply this patch.

Best regards,
Christian

[1] https://lists.gnu.org/archive/html/emacs-orgmode/2021-01/msg00151.html
-- 
....Christian.Garbs....................................https://www.cgarbs.de

It seemed like a good idea at the time.
>From 04888bb0146d0f0cf3bf82cea4f0ea1b761c1d08 Mon Sep 17 00:00:00 2001
From: Christian Garbs <mi...@cgarbs.de>
Date: Fri, 8 Jan 2021 20:39:29 +0100
Subject: [PATCH] ox-bb.el: Add BBCode exporter

* lisp/ox-bb.el: Add export backend for BBCode format.

* testing/lisp/test-ox-bb.el: Add tests for ox-bb.el.

* doc/org-manual.org (Exporting): Add section about BBCode export.

* etc/ORG-NEWS: Announce BBCode exporter as a new feature.
---
 doc/org-manual.org         |  29 +++
 etc/ORG-NEWS               |  10 +
 lisp/ox-bb.el              | 423 +++++++++++++++++++++++++++++++
 testing/lisp/test-ox-bb.el | 503 +++++++++++++++++++++++++++++++++++++
 4 files changed, 965 insertions(+)
 create mode 100644 lisp/ox-bb.el
 create mode 100644 testing/lisp/test-ox-bb.el

diff --git a/doc/org-manual.org b/doc/org-manual.org
index 20a0d1d7a..ff19b5111 100644
--- a/doc/org-manual.org
+++ b/doc/org-manual.org
@@ -12281,6 +12281,35 @@ It's just a jump to the left...
 ,#+END_JUSTIFYRIGHT
 #+end_example
 
+** BBCode Export
+:PROPERTIES:
+:DESCRIPTION: Exporting to BBCode.
+:END:
+#+cindex: BBCode export
+
+BBCode export produces an output file containing BBCode markup.  Code
+sections are exported as =[code]= blocks for the GeSHI formatter found
+in some web forums.
+
+*** BBCode export commands
+:PROPERTIES:
+:UNNUMBERED: notoc
+:END:
+
+#+attr_texinfo: :sep ,
+- {{{kbd(C-c C-e b f)}}} (~org-bb-export-to-bbcode~) ::
+  #+kindex: C-c C-e b f
+  #+findex: org-bb-export-to-bbcode
+
+  Export as a BBCode formatted text file with a =.bbcode= extension,
+  overwriting without warning.
+
+- {{{kbd(C-c C-e b b)}}} (~org-bb-export-to-bbcode~) ::
+  #+kindex: C-c C-e b b
+  #+findex: org-bb-export-as-bbcode
+
+  Export to a temporary buffer.  Does not create a file.
+
 ** Beamer Export
 :PROPERTIES:
 :DESCRIPTION: Producing presentations and slides.
diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS
index ba769224f..035abba1b 100644
--- a/etc/ORG-NEWS
+++ b/etc/ORG-NEWS
@@ -156,6 +156,16 @@ tags including from both buffer local and user defined persistent
 global list (~org-tag-alist~ and ~org-tag-persistent-alist~).  Now
 option ~org-complete-tags-always-offer-all-agenda-tags~ is honored.
 
+*** New BBCode export backend
+
+A new export backend generates BBCode as used in many internet forums.
+Code blocks will be exported as =[code]= blocks for the GeSHi
+formatter.  There are no configurable options.
+
+You can export to a temporary buffer using =<C-c C-e b b>= or to a
+file with extension =.bbcode= (overwritten without warning) via =<C-c
+C-e b f>=.
+
 ** Miscellaneous
 *** =org-goto-first-child= now works before first heading
 
diff --git a/lisp/ox-bb.el b/lisp/ox-bb.el
new file mode 100644
index 000000000..50271e675
--- /dev/null
+++ b/lisp/ox-bb.el
@@ -0,0 +1,423 @@
+;;; ox-bb.el --- BBCode Back-End for Org Export Engine -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2017-2021 Free Software Foundation, Inc.
+;; Author: org, wp, bbcode
+
+;; This file is part of ox-bb.
+
+;; GNU EMacs is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This library implements a BBCode back-end for Org exporter.  Code
+;; sections are formatted for the GeSHi formatter found in some web
+;; forums.  See Org manual for more information.
+
+;;; Code:
+
+(require 'ox)
+
+;;; Backend definition
+
+(org-export-define-backend 'bb
+  '((bold . org-bb-bold)
+    (center-block . org-bb-undefined)
+    (clock . org-bb-undefined)
+    (code . org-bb-code)
+    (drawer . org-bb-undefined)
+    (dynamic-block . org-bb-undefined)
+    (entity . org-bb-entity)
+    (example-block . org-bb-undefined)
+    (export-block . org-bb-undefined)
+    (export-snippet . org-bb-undefined)
+    (fixed-width . org-bb-fixed-width)
+    (footnote-definition . org-bb-footnote-definition)
+    (footnote-reference . org-bb-footnote-reference)
+    (headline . org-bb-headline)
+    (horizontal-rule . org-bb-undefined)
+    (inline-src-block . org-bb-undefined)
+    (inlinetask . org-bb-undefined)
+    (inner-template . org-bb-inner-template)
+    (italic . org-bb-italic)
+    (item . org-bb-item)
+    (keyword . org-bb-undefined)
+    (latex-environment . org-bb-undefined)
+    (latex-fragment . org-bb-undefined)
+    (line-break . org-bb-line-break)
+    (link . org-bb-link)
+    (node-property . org-bb-undefined)
+    (paragraph . org-bb-paragraph)
+    (plain-list . org-bb-plain-list)
+    (plain-text . org-bb-plain-text)
+    (planning . org-bb-undefined)
+    (property-drawer . org-bb-undefined)
+    (quote-block . org-bb-quote-block)
+    (radio-target . org-bb-undefined)
+    (section . org-bb-section)
+    (special-block . org-bb-undefined)
+    (src-block . org-bb-geshi-block)
+    (statistics-cookie . org-bb-undefined)
+    (strike-through . org-bb-strike-through)
+    (subscript . org-bb-undefined)
+    (superscript . org-bb-undefined)
+    (table . org-bb-undefined)
+    (table-cell . org-bb-undefined)
+    (table-row . org-bb-undefined)
+    (target . org-bb-undefined)
+    (template . org-bb-template)
+    (timestamp . org-bb-undefined)
+    (underline . org-bb-underline)
+    (verbatim . org-bb-verbatim)
+    (verse-block . org-bb-undefined))
+  :menu-entry
+  '(?b "Export to BBCode"
+       ((?b "As BBCode buffer" org-bb-export-as-bbcode)
+	(?f "As BBCode file" org-bb-export-to-bbcode))))
+
+;;; Helper methods
+
+(defun org-bb--as-block (text)
+  "Format TEXT as a block with leading and trailing newline."
+  (concat "\n" text "\n"))
+
+(defun org-bb--force-leading-newline (text)
+  "Make TEXT start with exactly one newline."
+  (replace-regexp-in-string "\\`\n*" "\n" text))
+
+(defun org-bb--format-headline (text level)
+  "Format TEXT as a headline of the given LEVEL."
+  (let ((indent (cl-case level
+		  (0 "")
+		  (1 "# ")
+		  (2 "== ")
+		  (3 "+++ ")
+		  (4 ":::: ")
+		  (5 "----- ")
+		  (t (error "Headline level `%s' is not defined yet" level)))))
+    (concat
+     (org-bb--put-in-tag
+      "b" (org-bb--put-in-tag
+	   "u" (concat indent text)))
+     "\n\n")))
+
+(defun org-bb--put-in-tag (tag contents &optional attributes)
+  "Puts the BBcode tag TAG around the CONTENTS string.
+Optional ATTRIBUTES for the tag can be given as an alist of
+key/value pairs (both strings)."
+  (let ((attribute-string (if attributes
+			      (mapconcat (function (lambda (attribute)
+						     (let ((key (car attribute))
+							   (value (cadr attribute)))
+						       (format " %s=\"%s\"" key value))))
+					 attributes
+					 "")
+			    "")))
+    (format "[%s%s]%s[/%s]" tag attribute-string contents tag)))
+
+(defun org-bb--put-in-value-tag (tag contents value)
+  "Puts the BBcode tag TAG around the CONTENTS string.
+The VALUE is assigned directly to the tag instead of a normal
+key/value pair."
+  (format "[%s=%s]%s[/%s]" tag value contents tag))
+
+(defun org-bb--fix-url (url)
+  "Fix URL returned from `url-encode-url'.
+Older versions of Emacs (eg. 24.3 used in the Travis CI minimal
+image) prepend \"/\" to urls consisting only of an \"#anchor\"
+part.  We don't want this, because we need relative anchors.  Fix
+this the hard way."
+  (if (string-prefix-p "/#" url)
+      (substring url 1)
+    url))
+
+(defun org-bb--put-url (contents href)
+  "Puts the CONTENTS inside a [url] tag pointing to HREF.
+Automagically escapes the target URL."
+  (let* ((target (org-bb--fix-url (url-encode-url (org-link-unescape href))))
+	 (text   (or contents target)))
+    (org-bb--put-in-value-tag "url" text target)))
+
+(defun org-bb--remove-leading-newline (text)
+  "Remove a leading empty line from TEXT."
+  (replace-regexp-in-string "\\`\n" "" text))
+
+(defun org-bb--remove-trailing-newline (text)
+  "Remove the trailing newline from TEXT."
+  (replace-regexp-in-string "\n\\'" "" text))
+
+(defun org-bb--map-to-geshi-language (language)
+  "Map LANGUAGE from Org to GeSHi."
+  (cond ((string= language "elisp") "lisp")
+	((string= language "shell") "bash")
+	((string= language "sh")    "bash")
+	((string= language "") "plaintext")
+	(language)
+	(t "plaintext")))
+
+;;; Backend callbacks
+
+(defun org-bb-bold (_bold contents _info)
+  "Transcode a BOLD element from Org to BBCode.
+CONTENTS is the bold text, as a string.  INFO is
+  a plist used as a communication channel."
+  (org-bb--put-in-tag "b" contents))
+
+(defun org-bb-code (code _contents _info)
+  "Transcode a CODE element from Org to BBCode.
+CONTENTS is nil.  INFO is a plist used as a communication channel."
+  (org-bb--put-in-value-tag "font" (org-element-property :value code) "monospace"))
+
+(defun org-bb-entity (entity _contents _info)
+  "Transcode an ENTITY element from Org to BBCode.
+CONTENTS is the definition itself.  INFO is a plist used as a
+communication channel."
+  (org-element-property :html entity))
+
+(defun org-bb-geshi-block (code-block _contents info)
+  "Transcode a CODE-BLOCK element from Org to BBCode GeSHi plugin.
+CONTENTS is nil.  INFO is a plist holding
+contextual information."
+  (format "[geshi lang=%s]%s[/geshi]"
+	  (org-bb--map-to-geshi-language (org-element-property :language code-block))
+	  (org-bb--remove-trailing-newline
+	   (org-export-format-code-default code-block info))))
+
+(defun org-bb-fixed-width (fixed-width _contents _info)
+  "Transcode a FIXED-WIDTH element from Org to BBCode.
+CONTENTS is nil.  INFO is a plist holding contextual information."
+  (org-bb--put-in-tag "code"
+		      (concat "\n" (org-element-property :value fixed-width) "\n")))
+
+(defun org-bb-footnote-reference (footnote-reference _contents info)
+  "Transcode a FOOTNOTE-REFERENCE element from Org to BBCode.
+CONTENTS is nil.  INFO is a plist holding contextual information."
+  (if (eq (org-element-property :type footnote-reference) 'inline)
+      (error "Inline footnotes not supported yet")
+    (let ((n (org-export-get-footnote-number footnote-reference info)))
+      (format "^%d" n))))
+
+(defun org-bb-format-footnote-definition (fn)
+  "Format the footnote definition FN."
+  (let ((n (car fn))
+	(def (cdr fn)))
+    (format "^%d: %s" n def)))
+
+(defun org-bb-footnote-section (info)
+  "Format the footnote section.
+INFO is a plist used as a communication channel."
+  (let* ((fn-alist (org-export-collect-footnote-definitions info (plist-get info :parse-tree)))
+	 (fn-alist
+	  (cl-loop for (n _label raw) in fn-alist collect
+		   (cons n (org-trim (org-export-data raw info)))))
+	 (text (mapconcat 'org-bb-format-footnote-definition fn-alist "\n")))
+    (if fn-alist
+	(concat "\n" (org-bb--format-headline "Footnotes" 0) text "\n")
+      "")))
+
+(defun org-bb-headline (headline contents info)
+  "Transcode HEADLINE element from Org to BBCode.
+CONTENTS is the headline contents.  INFO is a plist used as
+a communication channel."
+  (let ((title (org-export-data (org-element-property :title headline) info))
+	(level (org-export-get-relative-level headline info)))
+    (if (org-element-property :footnote-section-p headline)
+	""
+      (concat
+       (org-bb--format-headline title level)
+       contents))))
+
+(defun org-bb-inner-template (contents info)
+  "Return body of document string after BBCode conversion.
+CONTENTS is the transcoded contents string.  INFO is a plist
+holding export options."
+  (concat
+   contents
+   (org-bb-footnote-section info)))
+
+(defun org-bb-italic (_italic contents _info)
+  "Transcode a ITALIC element from Org to BBCode.
+CONTENTS is the italic text, as a string.  INFO is
+  a plist used as a communication channel."
+  (org-bb--put-in-tag "i" contents))
+
+(defun org-bb-item (item contents info)
+  "Transcode a ITEM element from Org to BBCode.
+CONTENTS is the contents of the item, as a string.  INFO is
+  a plist used as a communication channel."
+  (let* ((plain-list (org-export-get-parent item))
+	 (type (org-element-property :type plain-list))
+	 (text (org-trim contents)))
+    (concat
+     "[*]"
+     (pcase type
+       (`descriptive
+	(let ((term (let ((tag (org-element-property :tag item)))
+		      (and tag (org-export-data tag info)))))
+	  (concat
+	   (org-bb--put-in-tag "i" (concat (org-trim term) ":"))
+	   " "
+	   ))))
+     text
+     "\n")))
+
+(defun org-bb-line-break (_line-break _contents _info)
+  "Transcode a LINE-BREAK object from Org to BBCode.
+CONTENTS is nil.  INFO is a plist holding contextual
+information."
+  "[br]_[/br]\n")
+
+(defun org-bb-link (link contents _info)
+  "Transcode a LINK element from Org to BBCode.
+CONTENTS is the contents of the link, as a string.  INFO is
+  a plist used as a communication channel."
+  (let ((type (org-element-property :type link))
+	(path (org-element-property :path link))
+	(raw  (org-element-property :raw-link link)))
+    (cond
+     ((string= type "fuzzy")
+      (cond
+       ((string-prefix-p "about:" raw)
+	(org-bb--put-url contents raw))
+       (t (error "Unknown fuzzy LINK type encountered: `%s'" raw))))
+     ((member type '("http" "https"))
+      (org-bb--put-url contents (concat type ":" path)))
+     (t (error "LINK type `%s' not yet supported" type)))))
+
+(defun org-bb-paragraph (_paragraph contents _info)
+  "Transcode a PARAGRAPH element from Org to BBCode.
+CONTENTS is the contents of the paragraph, as a string.  INFO is
+  a plist used as a communication channel."
+  (org-trim contents))
+
+(defun org-bb-plain-list (plain-list contents _info)
+  "Transcode a PLAIN-LIST element from Org to BBCode.
+CONTENTS is the contents of the plain-list, as a string.  INFO is
+  a plist used as a communication channel."
+  (let ((type (org-element-property :type plain-list))
+	(content-block (org-bb--as-block (org-trim contents))))
+    (concat
+     (pcase type
+       (`descriptive (org-bb--put-in-tag "list" content-block))
+       (`unordered (org-bb--put-in-tag "list" content-block))
+       (`ordered (org-bb--put-in-value-tag "list" content-block "1"))
+       (other (error "PLAIN-LIST type `%s' not yet supported" other)))
+     "\n")))
+
+(defun org-bb-plain-text (text _info)
+  "Transcode a TEXT string from Org to BBCode.
+INFO is a plist used as a communication channel."
+  text)
+
+(defun org-bb-quote-block (_quote-block contents _info)
+  "Transcode a QUOTE-BLOCK element from Org to BBCode.
+CONTENTS holds the contents of the block.  INFO is a plist used
+as a communication channel."
+  (org-bb--put-in-tag "quote" (org-bb--force-leading-newline contents)))
+
+(defun org-bb-section (_section contents _info)
+  "Transcode a SECTION element from Org to BBCode.
+CONTENTS is the contents of the section, as a string.  INFO is a
+  plist used as a communication channel."
+  (org-trim contents))
+
+(defun org-bb-strike-through (_strike-through contents _info)
+  "Transcode a STRIKE-THROUGH element from Org to BBCode.
+CONTENTS is the text with strike-through markup, as a string.
+  INFO is a plist used as a communication channel."
+  (org-bb--put-in-tag "s" contents))
+
+(defun org-bb-template (contents _info)
+  "Return complete document string after BBCode conversion.
+CONTENTS is the transcoded contents string.  INFO is a plist
+holding export options."
+  contents)
+
+(defun org-bb-undefined (element &optional _contents _info)
+  "Throw an error when an unsupported ELEMENT is encountered."
+  (error "ELEMENT type `%s' not implemented yet" (car element)))
+
+(defun org-bb-underline (_underline contents _info)
+  "Transcode a UNDERLINE element from Org to BBCode.
+CONTENTS is the underlined text, as a string.  INFO is
+  a plist used as a communication channel."
+  (org-bb--put-in-tag "u" contents))
+
+(defun org-bb-verbatim (verbatim _contents _info)
+  "Transcode a VERBATIM element from Org to BBCode.
+CONTENTS is nil.  INFO is a plist used as a communication channel."
+  (org-bb--put-in-value-tag "font" (org-element-property :value verbatim) "monospace"))
+
+;;; Export methods
+
+;;;###autoload
+(defun org-bb-export-as-bbcode
+  (&optional async subtreep visible-only)
+  "Export current buffer to a BBCode buffer.
+
+If narrowing is active in the current buffer, only export its
+narrowed part.
+
+If a region is active, export that region.
+
+A non-nil optional argument ASYNC means the process should happen
+asynchronously.  The resulting buffer should be accessible
+through the `org-export-stack' interface.
+
+When optional argument SUBTREEP is non-nil, export the sub-tree
+at point, extracting information from the headline properties
+first.
+
+When optional argument VISIBLE-ONLY is non-nil, don't export
+contents of hidden elements.
+
+Export is done in a buffer named \"*Org BBCode Export*\"."
+  (interactive)
+  (org-export-to-buffer 'bb "*Org BBCode Export*"
+    async subtreep visible-only (lambda () (when (require 'bbcode-mode nil :noerror)
+					     (bbcode-mode)))))
+
+;;;###autoload
+(defun org-bb-export-to-bbcode
+  (&optional async subtreep visible-only)
+  "Export current buffer to a BBCode file.
+
+If narrowing is active in the current buffer, only export its
+narrowed part.
+
+If a region is active, export that region.
+
+A non-nil optional argument ASYNC means the process should happen
+asynchronously.  The resulting buffer should be accessible
+through the `org-export-stack' interface.
+
+When optional argument SUBTREEP is non-nil, export the sub-tree
+at point, extracting information from the headline properties
+first.
+
+When optional argument VISIBLE-ONLY is non-nil, don't export
+contents of hidden elements.
+
+Return output file's name."
+  (interactive)
+  (let* ((extension ".bbcode")
+	 (file (org-export-output-file-name extension subtreep))
+	 (org-export-coding-system org-html-coding-system))
+    (org-export-to-file 'bb file
+      async subtreep visible-only)))
+
+;;; Register file
+
+(provide 'ox-bb)
+
+;;; ox-bb.el ends here
diff --git a/testing/lisp/test-ox-bb.el b/testing/lisp/test-ox-bb.el
new file mode 100644
index 000000000..b8ddc7f67
--- /dev/null
+++ b/testing/lisp/test-ox-bb.el
@@ -0,0 +1,503 @@
+;;; test-ox-bb.el --- Tests for ox-bb.el -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2017-2019, 2021  Christian Garbs
+
+;; Author: Christian Garbs <mi...@cgarbs.de>
+
+;; This file is not part of GNU Emacs.
+
+;; This program is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; Tests for ox-bb.el.
+
+;;; Code:
+
+(require 'ox-bb)
+
+;;; helper functions
+
+(defun test-org-bb-verbatim-regression ()
+  "Return t if verbatim blocks generate an extra newline.
+This lets the tests react to a possible regression introduced
+with 7d9e4da447 which was released with Org 9.1.14.  See
+https://lists.gnu.org/archive/html/emacs-orgmode/2021-01/msg00338.html
+for details."
+  (not (version< (org-release) "9.1.14")))
+
+;;; sanity check
+
+(ert-deftest test-org-bb/assert ()
+  (should t))
+
+;;; tests of internal functions
+
+;;; org-bb--as-block
+
+(ert-deftest test-org-bb/as-block/plain ()
+  (should (equal( org-bb--as-block "some text\nline two")
+		"\nsome text\nline two\n")))
+
+;;; org-bb--force-leading-newline
+
+(ert-deftest test-org-bb/force-leading-newline/add-missing-newline ()
+  (should (equal( org-bb--force-leading-newline "some text")
+		"\nsome text")))
+
+(ert-deftest test-org-bb/force-leading-newline/keep-existing-newline ()
+  (should (equal( org-bb--force-leading-newline "\nonly one newline")
+		"\nonly one newline")))
+
+(ert-deftest test-org-bb/force-leading-newline/remove-additional-newlines ()
+  (should (equal( org-bb--force-leading-newline "\n\nsome text")
+		"\nsome text")))
+
+(ert-deftest test-org-bb/force-leading-newline/keep-newlines-within ()
+  (should (equal( org-bb--force-leading-newline "\nline 1\nline 2\n")
+		"\nline 1\nline 2\n")))
+
+;;; org-bb--format-headline
+
+(ert-deftest test-org-bb/format-headline/level-0 ()
+  (should (equal( org-bb--format-headline "some text" 0)
+		"[b][u]some text[/u][/b]\n\n")))
+
+(ert-deftest test-org-bb/format-headline/level-1 ()
+  (should (equal( org-bb--format-headline "some text" 1)
+		"[b][u]# some text[/u][/b]\n\n")))
+
+(ert-deftest test-org-bb/format-headline/level-2 ()
+  (should (equal( org-bb--format-headline "some text" 2)
+		"[b][u]== some text[/u][/b]\n\n")))
+
+(ert-deftest test-org-bb/format-headline/level-3 ()
+  (should (equal( org-bb--format-headline "some text" 3)
+		"[b][u]+++ some text[/u][/b]\n\n")))
+
+(ert-deftest test-org-bb/format-headline/level-4 ()
+  (should (equal( org-bb--format-headline "some text" 4)
+		"[b][u]:::: some text[/u][/b]\n\n")))
+
+(ert-deftest test-org-bb/format-headline/level-5 ()
+  (should (equal( org-bb--format-headline "some text" 5)
+		"[b][u]----- some text[/u][/b]\n\n")))
+
+;;; org-bb--put-in-tag
+
+(ert-deftest test-org-bb/put-in-tag/no-attribute ()
+  (should (equal (org-bb--put-in-tag "p" "foo")
+		 "[p]foo[/p]")))
+
+(ert-deftest test-org-bb/put-in-tag/single-attribute ()
+  (should (equal (org-bb--put-in-tag "style" "foo" '(("size" "30px")))
+		 "[style size=\"30px\"]foo[/style]")))
+
+(ert-deftest test-org-bb/put-in-tag/multiple-attributes ()
+  (should (equal (org-bb--put-in-tag "style" "foo" '(("color" "#00FF00") ("size" "30px")))
+		 "[style color=\"#00FF00\" size=\"30px\"]foo[/style]")))
+
+;;; org-bb--put-in-value-tag
+
+(ert-deftest test-org-bb/put-in-value-tag/plain ()
+  (should (equal (org-bb--put-in-value-tag "url" "foo" "file.htm")
+		 "[url=file.htm]foo[/url]")))
+
+;;; org-bb--put-url
+
+(ert-deftest test-org-bb/put-url/plain ()
+  (should (equal (org-bb--put-url "some text" "https://example.com/";)
+		 "[url=https://example.com/]some text[/url]")))
+
+(ert-deftest test-org-bb/put-url/empty ()
+  (should (equal (org-bb--put-url nil "https://example.com/";)
+		 "[url=https://example.com/]https://example.com/[/url]";)))
+
+(ert-deftest test-org-bb/put-url/anchor ()
+  (should (equal (org-bb--put-url "anchor text" "#anchor")
+		 "[url=#anchor]anchor text[/url]")))
+
+(ert-deftest test-org-bb/put-url/encode-url-only-once ()
+  (should (equal (org-bb--put-url "baz" "http://foo/%20bar";)
+		 "[url=http://foo/%20bar]baz[/url]";)))
+
+;;; org-bb--remove-leading-newline
+
+(ert-deftest test-org-bb/remove-leading-newline/remove ()
+  (should (equal( org-bb--remove-leading-newline "\nsome text")
+		"some text")))
+
+(ert-deftest test-org-bb/remove-leading-newline/keep-text-before-first-newline ()
+  (should (equal( org-bb--remove-leading-newline "no empty line\nsome more text\n")
+		"no empty line\nsome more text\n")))
+
+(ert-deftest test-org-bb/remove-leading-newline/only-remove-first-newline ()
+  (should (equal( org-bb--remove-leading-newline "\n\nsome text")
+		"\nsome text")))
+
+(ert-deftest test-org-bb/remove-leading-newline/keep-newlines-within ()
+  (should (equal( org-bb--remove-leading-newline "\nline 1\nline 2")
+		"line 1\nline 2")))
+
+(ert-deftest test-org-bb/remove-leading-newline/dont-fail-with-no-newline ()
+  (should (equal( org-bb--remove-leading-newline "some text")
+		"some text")))
+
+;;; org-bb--remove-trailing-newline
+
+(ert-deftest test-org-bb/remove-trailing-newline/remove ()
+  (should (equal( org-bb--remove-trailing-newline "some text\n")
+		"some text")))
+
+(ert-deftest test-org-bb/remove-trailing-newline/keep-text-after-last-newline ()
+  (should (equal( org-bb--remove-trailing-newline "some text\nno empty line")
+		"some text\nno empty line")))
+
+(ert-deftest test-org-bb/remove-trailing-newline/only-remove-last-newline ()
+  (should (equal( org-bb--remove-trailing-newline "some text\n\n")
+		"some text\n")))
+
+(ert-deftest test-org-bb/remove-trailing-newline/keep-newlines-within ()
+  (should (equal( org-bb--remove-trailing-newline "line 1\nline 2\n")
+		"line 1\nline 2")))
+
+(ert-deftest test-org-bb/remove-trailing-newline/dont-fail-with-no-newline ()
+  (should (equal( org-bb--remove-trailing-newline "some text")
+		"some text")))
+
+;;; org-bb--map-to-geshi-language
+
+(ert-deftest test-org-bb/map-to-geshi-language/unchanged ()
+  (should (equal( org-bb--map-to-geshi-language "java")
+		"java")))
+
+(ert-deftest test-org-bb/map-to-geshi-language/changed ()
+  (should (equal( org-bb--map-to-geshi-language "elisp")
+		"lisp")))
+
+(ert-deftest test-org-bb/map-to-geshi-language/nil ()
+  (should (equal( org-bb--map-to-geshi-language nil)
+		"plaintext")))
+
+(ert-deftest test-org-bb/map-to-geshi-language/empty ()
+  (should (equal( org-bb--map-to-geshi-language "")
+		"plaintext")))
+
+;;; end-to-end tests: compare given input file with expected export output
+
+(defun test-org-bb-export (input)
+  "Transform INPUT to BBCode and return the result."
+  (org-test-with-temp-text input
+			   (org-bb-export-as-bbcode)
+			   (with-current-buffer "*Org BBCode Export*"
+			     (buffer-substring-no-properties (point-min) (point-max)))))
+
+(ert-deftest test-org-bb/export-bold ()
+  (should (equal (test-org-bb-export "foo *BAR* baz
+")
+		 "foo [b]BAR[/b] baz
+")))
+
+(ert-deftest test-org-bb/export-code ()
+  (should (equal (test-org-bb-export "foo ~BAR~ baz
+")
+		 "foo [font=monospace]BAR[/font] baz
+")))
+
+(ert-deftest test-org-bb/export-entity ()
+  (should (equal (test-org-bb-export "This is *bold* and this is in \\ast{}asterisks\\ast{}.
+")
+		 "This is [b]bold[/b] and this is in &lowast;asterisks&lowast;.
+")))
+
+(ert-deftest test-org-bb/export-fixed-width ()
+  ;; Where does the double newline before paragraph 2 come from?
+  ;; in Org 9.1 there was only a single newline as expected.
+  ;; A regression?
+  (should (equal (test-org-bb-export "paragraph 1
+
+: verbatim line
+:   indented verbatim line
+
+paragraph 2")
+		 (if (test-org-bb-verbatim-regression)
+		     "paragraph 1
+
+[code]
+verbatim line
+  indented verbatim line
+[/code]
+
+
+paragraph 2
+"
+		   "paragraph 1
+
+[code]
+verbatim line
+  indented verbatim line
+[/code]
+
+paragraph 2
+"))))
+
+(ert-deftest test-org-bb/export-footnote-multiple ()
+  (should (equal (test-org-bb-export "foo[fn:1] bar[fn:2]
+* Footnotes
+
+[fn:1] foo
+[fn:2] bar
+")
+		 "foo^1 bar^2
+
+[b][u]Footnotes[/u][/b]
+
+^1: foo
+^2: bar
+")))
+
+(ert-deftest test-org-bb/export-footnote-plain ()
+  (should (equal (test-org-bb-export "bar[fn:1]
+* Footnotes
+
+[fn:1] foo
+")
+		 "bar^1
+
+[b][u]Footnotes[/u][/b]
+
+^1: foo
+")))
+
+(ert-deftest test-org-bb/export-geshi-block-without-language ()
+  (should (equal (test-org-bb-export "#+BEGIN_SRC
+package foo;
+/* dummy dummy */
+#+END_SRC
+")
+		 "[geshi lang=plaintext]package foo;
+/* dummy dummy */[/geshi]
+")))
+
+(ert-deftest test-org-bb/export-geshi-block ()
+  (should (equal (test-org-bb-export "#+BEGIN_SRC java
+package foo;
+/* dummy dummy */
+#+END_SRC
+")
+		 "[geshi lang=java]package foo;
+/* dummy dummy */[/geshi]
+")))
+
+(ert-deftest test-org-bb/export-headline-lv1 ()
+  (should (equal (test-org-bb-export "* TOPIC
+")
+		 "[b][u]# TOPIC[/u][/b]
+")))
+
+(ert-deftest test-org-bb/export-headline-lv2 ()
+  (should (equal (test-org-bb-export "* dummy
+** TOPIC
+")
+		 "[b][u]# dummy[/u][/b]
+
+[b][u]== TOPIC[/u][/b]
+")))
+
+(ert-deftest test-org-bb/export-headline-lv3 ()
+  (should (equal (test-org-bb-export "* dummy
+** dummy
+*** TOPIC
+")
+		 "[b][u]# dummy[/u][/b]
+
+[b][u]== dummy[/u][/b]
+
+[b][u]+++ TOPIC[/u][/b]
+")))
+
+(ert-deftest test-org-bb/export-headline-lv4 ()
+  (should (equal (test-org-bb-export "* dummy
+** dummy
+*** dummy
+**** TOPIC
+")
+		 "[b][u]# dummy[/u][/b]
+
+[b][u]== dummy[/u][/b]
+
+[b][u]+++ dummy[/u][/b]
+
+[b][u]:::: TOPIC[/u][/b]
+")))
+
+(ert-deftest test-org-bb/export-headline-lv5 ()
+  (should (equal (test-org-bb-export "* dummy
+** dummy
+*** dummy
+**** dummy
+***** TOPIC
+")
+		 "[b][u]# dummy[/u][/b]
+
+[b][u]== dummy[/u][/b]
+
+[b][u]+++ dummy[/u][/b]
+
+[b][u]:::: dummy[/u][/b]
+
+[b][u]----- TOPIC[/u][/b]
+")))
+
+(ert-deftest test-org-bb/export-italic ()
+  (should (equal (test-org-bb-export "foo /BAR/ baz
+")
+		 "foo [i]BAR[/i] baz
+")))
+
+(ert-deftest test-org-bb/export-line-break ()
+  (should (equal (test-org-bb-export "foo\\\\
+bar
+")
+		 "foo[br]_[/br]
+bar
+")))
+
+(ert-deftest test-org-bb/export-link-about ()
+  (should (equal (test-org-bb-export "[[about:config][bar]]
+")
+		 "[url=about:config]bar[/url]
+")))
+
+(ert-deftest test-org-bb/export-link-empty ()
+  (should (equal (test-org-bb-export "http://example.com/
+")
+		 "[url=http://example.com/]http://example.com/[/url]
+")))
+
+(ert-deftest test-org-bb/export-link-encode-url-only-once ()
+  (should (equal (test-org-bb-export "[[http://foo/%20bar][baz]]
+")
+		 "[url=http://foo/%20bar]baz[/url]
+")))
+
+(ert-deftest test-org-bb/export-link-encode-url ()
+  (should (equal (test-org-bb-export "[[http://foo/ bar][baz]]
+")
+		 "[url=http://foo/%20bar]baz[/url]
+")))
+
+(ert-deftest test-org-bb/export-link-http ()
+  (should (equal (test-org-bb-export "[[http://foo/][bar]]
+")
+		 "[url=http://foo/]bar[/url]
+")))
+
+(ert-deftest test-org-bb/export-link-https ()
+  (should (equal (test-org-bb-export "[[https://foo/][bar]]
+")
+		 "[url=https://foo/]bar[/url]
+")))
+
+(ert-deftest test-org-bb/export-multiline-paragraph ()
+  (should (equal (test-org-bb-export "foo
+bar
+")
+		 "foo
+bar
+")))
+
+(ert-deftest test-org-bb/export-multiple-paragraphs ()
+  (should (equal (test-org-bb-export "foo
+
+bar
+")
+		 "foo
+
+bar
+")))
+
+(ert-deftest test-org-bb/export-plain-list-descriptive ()
+  (should (equal (test-org-bb-export "- foo :: pokey
+- bar :: hokey
+")
+		 "[list]
+[*][i]foo:[/i] pokey
+[*][i]bar:[/i] hokey
+[/list]
+")))
+
+(ert-deftest test-org-bb/export-plain-list-ordered ()
+  (should (equal (test-org-bb-export "1. foo
+2. bar
+")
+		 "[list=1]
+[*]foo
+[*]bar
+[/list]
+")))
+
+(ert-deftest test-org-bb/export-plain-list-unordered ()
+  (should (equal (test-org-bb-export "- foo
+- bar
+")
+		 "[list]
+[*]foo
+[*]bar
+[/list]
+")))
+
+(ert-deftest test-org-bb/export-quote-block ()
+  (should (equal (test-org-bb-export "#+BEGIN_QUOTE
+Somebody
+said
+this.
+#+END_QUOTE
+")
+		 "[quote]
+Somebody
+said
+this.
+[/quote]
+")))
+
+(ert-deftest test-org-bb/export-single-paragraph ()
+  (should (equal (test-org-bb-export "foo
+")
+		 "foo
+")))
+
+(ert-deftest test-org-bb/export-strike-through ()
+  (should (equal (test-org-bb-export "foo +BAR+ baz
+")
+		 "foo [s]BAR[/s] baz
+")))
+
+(ert-deftest test-org-bb/export-underline ()
+  (should (equal (test-org-bb-export "foo _BAR_ baz
+")
+		 "foo [u]BAR[/u] baz
+")))
+
+(ert-deftest test-org-bb/export-verbatim ()
+  (should (equal (test-org-bb-export "foo =BAR= baz
+")
+		 "foo [font=monospace]BAR[/font] baz
+")))
+
+
+
+(provide 'test-ox-bb)
+
+;;; test-ox-bb.el ends here
-- 
2.20.1

Reply via email to