README: Update
[orgmuse.git] / org.pl
blob7e8eae0f159cf8cf394847187547132d903e3644
1 #!/usr/bin/env perl
2 # ====================[ org.pl ]====================
4 =head1 NAME
6 org - An Oddmuse module for marking up Oddmuse Wiki pages according to the
7 Wiki Org standard, a Wiki-agnostic syntax scheme.
9 =head1 INSTALLATION
11 org is easily installable; move this file into the B<wiki/modules/>
12 directory for your Oddmuse Wiki.
14 =cut
15 package OddMuse;
17 $ModulesDescription .= '<p>See <a href="http://oddmuse.org/wiki/Org_Markup_Extension">Org Markup Extension</a></p>';
19 use Data::Dumper; # For debugging
21 # ....................{ CONFIGURATION }....................
23 =head1 CONFIGURATION
25 org is easily configurable; set these variables in the
26 B<wiki/config.pl> file for your Oddmuse Wiki.
28 =cut
29 use vars qw($OrgLineBreaks
30 $OrgTableCellsContainBlockLevelElements
31 $FootnoteFormat
32 $FootnoteSeparator
33 $FootnotesPattern
34 $FootnotesHeaderText
35 $FootnoteAnonymousCount
36 %FootnoteRecord
37 @FootnoteLabels);
39 =head2 $OrgEmphasisRegexpPattern
41 Regular expression to match emphasized text. On successful match, the
42 specific marker used for emphasis (i.e, one of C<*> C</> C<_> C<=>
43 C<+> C<~>) and the text between those markers is assumed to be
44 available in the variables C<$+{marker}> and C<$+{text}> respectively.
46 =cut
48 my $OrgEmphasisRegexpPattern =
49 qr! \G
50 (?<pre>^|(?<=[\ \t('\"{\n]))
52 (?<marker>[=*/_+~]) # match verbatim marker first
53 (?<text>
54 \S |
55 (?<pre_border>[^\ \t\r\n,'\"])
56 (?<body>[^\n]*?(?:\n[^\n]*?){0,3})
57 (?<post_border>[^\ \t\r\n,'\"])
59 \k<marker>
61 (?<post>(?=[-\ \t.,:?;'\")\n])|$)
62 !x;
64 my $OrgEmphasisStartPattern =
65 qr@ \G
66 (?<pre>^|(?<=[\ \t('\"{\n]))
67 (?<marker>[=*/_+~])
68 (?<pre_border>[^\ \t\r\n,'\"])
69 @x;
71 my $OrgEmphasisEndPattern =
72 qr@ \G
73 (?<post_border>[^\ \t\r\n,'\"])
74 (?<marker>[=*/_+~])
75 (?<post>(?=[-\ \t.,:?;'\")\n])|$)
76 @x;
78 my %OrgEmphasisAlist = ('**' => 'strong',
79 '//' => 'em',
80 '__' => 'u',
81 '==' => 'code', # Text between `=' marker is
82 # NEVER parsed for org syntax.
83 '~~' => 'tt', # Text between `~' marker is
84 # rendered in monospace font.
85 # It is parsed for Org syntax.
86 '++' => 'del',
87 '*' => 'strong',
88 '/' => 'em',
89 '_' => 'u',
90 '=' => 'code', # Text between `=' marker is
91 # NEVER parsed for org syntax.
92 '~' => 'tt', # Text between `~' marker is
93 # rendered in monospace font.
94 # It is parsed for Org syntax.
95 '+' => 'del',
98 =head2 $OrgLineBreaks
100 A boolean that, if true, causes this extension to convert single
101 newlines in page text to genuine linebreaks (i.e., the <br> tag) in
102 the HTML for that page. (If false, this extension consumes single
103 newlines without actually converting them into anything; they will be
104 ignored, wherever found.)
106 Irregardless of this booleans setting, this extension always converts
107 two newlines to a paragraph break (i.e., the <p> tag).
109 =cut
110 $OrgLineBreaks = 0;
112 =head2 $OrgTableCellsContainBlockLevelElements
114 A boolean that, if true, permits table cell markup to embed block
115 level elements in table cells. (By default, this boolean is false.)
117 You are encouraged to enable this boolean, as it significantly
118 improves the "stuff" you can do with Wiki Org table syntax. For
119 example, enabling this boolean permits you to embed nested lists in
120 tables.
122 Block level elements are such "high-level" entities as paragraphs,
123 blockquotes, list items, and so on. Thus, enabling this boolean
124 permits you to embed multiple paragraphs, blockquotes, and so on in
125 individual table cells.
127 Please note: enabling this boolean permits non-conformant syntax --
128 that is, syntax which no longer conforms to the Wiki Org standard. (In
129 general, unless you have significant amounts of Wiki Org table markup
130 strictly conforming to the Wiki Org standard, this shouldn't be an
131 issue.)
133 Please note: enabling this boolean also requires you explicitly close
134 the last table cell of a cell with a "|" character. (This character is
135 optional under the Wiki Org standard, but not under this
136 non-conformant alteration.)
138 =cut
139 $OrgTableCellsContainBlockLevelElements = 0;
141 =head2 $OrgTableHtmlAttributes
143 Html attributes that apply to an Org table. The default setting draws
144 rules at the top and bottom of the table and between the row and
145 column groups.
147 =cut
148 $OrgTableHtmlAttributes = 'border="2" rules="groups" frame="hsides"';
150 =head2 $FootnoteFormat
152 Format specifier for typesetting of footnote references. Replace
153 C<%s> specifier in this string with footnote number.
155 The default value is C<<sup>%s</sup>>. i.e., Typeset footnote
156 references as superscripts.
158 =cut
159 $FootnoteFormat = '<sup>%s</sup>';
161 =head2 $FootnoteSeparator
163 =cut
164 $FootnoteSeparator = ', ';
166 =head2 $FootnotesHeaderText
168 Outline header text for footnote definitions.
170 =cut
171 $FootnotesHeaderText = 'Footnotes:';
173 =head2 $FootnotesPattern
175 Replace text matching this regular expression with footnote
176 definitions. If a page specifies no such markup, emit footnote
177 definitions between the content and footer for that page.
179 Default value is C<\&lt;footnotes\&gt;[ \t]*(\n|$)>.
181 =cut
182 $FootnotesPattern = '\&lt;footnotes\&gt;[ \t]*(\n|$)';
184 =head2 MARKUP
186 Org handles additional markup resembling:
188 <footnotes>
190 See C<$FootnotesPattern>.
192 =cut
194 # ....................{ INITIALIZATION }....................
195 push(@MyInitVariables, \&OrgInit);
197 # A boolean that is true if the "orgaddition.pl" module is also installed.
198 my $OrgIsOrgAddition;
200 # A boolean set by OrgTextMarkupRule() to true, if a new table cell
201 # has just been started. This allows testing, elsewhere, of whether we
202 # are at the start of a a new table cell. Why test that? Because. If
203 # we are indeed at the start of a a new table cell, we should behave
204 # as if the "$bol_fixme" boolean is true: we should allow block level
205 # elements at the start of this new table cell.
207 # Of course, we have to set this to false immediately after matching
208 # past the start of that table cell. This is what RunMyRulesOrg()
209 # does.
210 my $OrgIsTableCellBol;
212 # A regular expression matching Wiki Org-style table cells.
213 my $OrgTableCellPattern = '[ \t]*(\|+)(=)?\n?([ \t]*)';
215 # A regular rexpression matching a horizontal ruler in an Org table.
216 # The regex doesn't include leading and trailing newlines.
217 my $OrgTableHrulePattern = '[ \t]*\|([-]+[+])*([-]+)\|[ \t]*';
219 # A regular expression matching Wiki Org-style pipe delimiters in
220 # links.
221 my $OrgLinkPipePattern = '[ \t]*\]\[[ \t]*';
223 # A regular expression matching Wiki Org-style link text. This
224 # expression takes into account the fact that such text is always
225 # optional.
226 my $OrgLinkTextPattern = "($OrgLinkPipePattern(.+?))?";
228 # The html tag and string of html tag attributes for the current Org
229 # header. This prevents an otherwise necessary, costly evaluation of
230 # test statements resembling:
232 # if (InElement('h1') or InElement('h2') or InElement('h3') or
233 # InElement('h4') or InElement('h5') or InElement('h6')) { ... }
235 # As Org headers cannot span blocks or lines, this should be a safe
236 # caching.
237 my ($OrgHeaderHtmlTag, $OrgHeaderHtmlTagAttr);
239 # For list of languages supported by Kate, see
240 # %NativeLanguageToKateLanguage in syntax-highlight.pl.
242 my %OrgLanguageToKateLanguage =
244 # ".desktop"=>".desktop",
245 # "4GL"=>"4GL",
246 # "4GL-PER"=>"4GL-PER",
247 # "ABC"=>"ABC",
248 # "AHDL"=>"AHDL",
249 # "ANSI C89"=>"ANSI C89",
250 # "ASP"=>"ASP",
251 # "AVR Assembler"=>"AVR Assembler",
252 "awk"=>"AWK",
253 "ada"=>"Ada",
254 # "Alerts"=>"Alerts",
255 # "Ansys"=>"Ansys",
256 # "Apache Configuration"=>"Apache Configuration",
257 # "Asm6502"=>"Asm6502",
258 "sh"=>"Bash",
259 "bibtex"=>"BibTeX",
260 "c"=>"C",
261 # "C#"=>"C#",
262 "c++"=>"C++",
263 # "CGiS"=>"CGiS",
264 # "CMake"=>"CMake",
265 "css"=>"CSS",
266 # "CSS/PHP"=>"CSS/PHP",
267 # "CUE Sheet"=>"CUE Sheet",
268 # "Cg"=>"Cg",
269 "change-log"=>"ChangeLog",
270 # "Cisco"=>"Cisco",
271 # "Clipper"=>"Clipper",
272 # "ColdFusion"=>"ColdFusion",
273 "lisp"=>"Common Lisp",
274 "emacs-lisp"=>"Common Lisp",
275 # "Component-Pascal"=>"Component-Pascal",
276 # "D"=>"D",
277 # "Debian Changelog"=>"Debian Changelog",
278 # "Debian Control"=>"Debian Control",
279 "diff"=>"Diff",
280 # "Doxygen"=>"Doxygen",
281 # "E Language"=>"E Language",
282 # "Eiffel"=>"Eiffel",
283 # "Email"=>"Email",
284 # "Euphoria"=>"Euphoria",
285 "fortran"=>"Fortran",
286 # "FreeBASIC"=>"FreeBASIC",
287 # "GDL"=>"GDL",
288 # "GLSL"=>"GLSL",
289 # "GNU Assembler"=>"GNU Assembler",
290 # "GNU Gettext"=>"GNU Gettext",
291 "html"=>"HTML",
292 # "Haskell"=>"Haskell",
293 # "IDL"=>"IDL",
294 # "ILERPG"=>"ILERPG",
295 # "INI Files"=>"INI Files",
296 # "Inform"=>"Inform",
297 # "Intel x86 (NASM)"=>"Intel x86 (NASM)",
298 # "JSP"=>"JSP",
299 "java"=>"Java",
300 "javascript"=>"JavaScript",
301 "JavaScript/PHP"=>"JavaScript/PHP",
302 # "Javadoc"=>"Javadoc",
303 # "KBasic"=>"KBasic",
304 # "Kate File Template"=>"Kate File Template",
305 # "LDIF"=>"LDIF",
306 # "LPC"=>"LPC",
307 "latex"=>"LaTeX",
308 # "Lex/Flex"=>"Lex/Flex",
309 # "LilyPond"=>"LilyPond",
310 # "Literate Haskell"=>"Literate Haskell",
311 # "Logtalk"=>"Logtalk",
312 # "Lua"=>"Lua",
313 # "M3U"=>"M3U",
314 # "MAB-DB"=>"MAB-DB",
315 # "MIPS Assembler"=>"MIPS Assembler",
316 "makefile"=>"Makefile",
317 # "Mason"=>"Mason",
318 # "Matlab"=>"Matlab",
319 # "Modula-2"=>"Modula-2",
320 # "Music Publisher"=>"Music Publisher",
321 # "Objective Caml"=>"Objective Caml",
322 # "Objective-C"=>"Objective-C",
323 "octave"=>"Octave",
324 # "PHP (HTML)"=>"PHP (HTML)",
325 # "PHP/PHP"=>"PHP/PHP",
326 # "POV-Ray"=>"POV-Ray",
327 "pascal"=>"Pascal",
328 "perl"=>"Perl",
329 # "PicAsm"=>"PicAsm",
330 # "Pike"=>"Pike",
331 # "PostScript"=>"PostScript",
332 # "Prolog"=>"Prolog",
333 # "PureBasic"=>"PureBasic",
334 "python"=>"Python",
335 # "Quake Script"=>"Quake Script",
336 # "R Script"=>"R Script",
337 # "REXX"=>"REXX",
338 # "RPM Spec"=>"RPM Spec",
339 # "RSI IDL"=>"RSI IDL",
340 # "RenderMan RIB"=>"RenderMan RIB",
341 "ruby"=>"Ruby",
342 "sgml"=>"SGML",
343 # "SML"=>"SML",
344 # "SQL (MySQL)"=>"SQL (MySQL)",
345 # "SQL (PostgreSQL)"=>"SQL (PostgreSQL)",
346 "sql"=>"SQL",
347 # "Sather"=>"Sather",
348 "scheme"=>"Scheme",
349 # "Sieve"=>"Sieve",
350 # "Spice"=>"Spice",
351 # "Stata"=>"Stata",
352 # "TI Basic"=>"TI Basic",
353 # "TaskJuggler"=>"TaskJuggler",
354 "tcl"=>"Tcl/Tk",
355 # "UnrealScript"=>"UnrealScript",
356 # "VHDL"=>"VHDL",
357 # "VRML"=>"VRML",
358 # "Velocity"=>"Velocity",
359 # "verilog"=>"Verilog",
360 # "WINE Config"=>"WINE Config",
361 # "Wikimedia"=>"Wikimedia",
362 "xml"=>"XML",
363 "nxml"=>"XML",
364 # "Yacc/Bison"=>"Yacc/Bison",
365 # "de_DE"=>"de_DE",
366 # "en_US"=>"en_US",
367 # "ferite"=>"ferite",
368 # "nl"=>"nl",
369 # "progress"=>"progress",
370 # "scilab"=>"scilab",
371 # "txt2tags"=>"txt2tags",
372 # "x.org Configuration"=>"x.org Configuration",
373 # "xHarbour"=>"xHarbour",
374 # "xslt"=>"xslt",
375 # "yacas"=>"yacas"
378 sub OrgInit {
379 $OrgIsOrgAddition = defined &OrgAdditionRule;
381 $OrgIsTableCellBol =
382 $OrgHeaderHtmlTag =
383 $OrgHeaderHtmlTagAttr = '';
385 # This is the "code magic" enabling block-level elements in multi-line
386 # table cells.
387 if ($OrgTableCellsContainBlockLevelElements) {
388 SetHtmlEnvironmentContainer('td');
389 SetHtmlEnvironmentContainer('th');
392 # Initialize footnote variables.
393 @FootnoteLabels = ();
394 %FootnoteList = {};
395 $FootnoteAnonymousCount = 0;
397 # FIXME: The following changes interfere with the bbcode extension.
398 # To achieve something similar, we often see sites with an InterMap
399 # entry called Self, eg. from http://emacswiki.org/InterMap: Self
400 # /cgi-bin/emacs? -- which allows you to link to Self:action=index.
402 # Permit page authors to link to URLs resembling:
403 # "See [[/?action=index|the site map]]."
405 # Which Oddmuse converts to HTML resembling:
406 # "See <a href="/?action=index">the site map</a>."
408 # When not using this extension, authors must add this Wiki's base URL:
409 # "See [[http://www.oddmuse.com/cgi-bin/oddmuse?action=index|the site map]]."
410 # my $UrlChars = '[-a-zA-Z0-9/@=+$_~*.,;:?!\'"()&#%]'; # see RFC 2396
411 # $FullUrlPattern = "((?:$UrlProtocols:|/)$UrlChars+)";
413 # Permit page authors to link to other pages having semicolons in
414 # their names.
416 # my $LinkCharsSansZero = "-;,.()' _1-9A-Za-z\x{0080}-\x{fffd}";
417 # my $LinkChars = $LinkCharsSansZero.'0';
418 # $FreeLinkPattern = "([$LinkCharsSansZero]|[$LinkChars][$LinkChars]+)";
421 # ....................{ MARKUP }....................
422 push(@MyRules,
423 \&OrgHeadingRule,
424 \&OrgTablesRule,
425 \&OrgGreaterElementsRule,
426 \&OrgListRule,
427 \&OrgElementsRule,
428 \&OrgObjectsRule
431 # Org heading rules must come after the TocRule.
432 $RuleOrder{\&OrgHeadingRule} = 100;
433 $RuleOrder{\&OrgTablesRule} = 110;
434 $RuleOrder{\&OrgGreaterElementsRule} = 120;
435 $RuleOrder{\&OrgListRule} = 130;
436 $RuleOrder{\&OrgElementsRule} = 140;
438 $RuleOrder{\&OrgObjectsRule} = 150;;
440 # Oddmuse's built-in ListRule conflicts with above OrgListRule. Thus,
441 # we ensure the latter is applied before the former.
442 $RuleOrder{\&ListRule} = 190;
443 # Oddmuse LinkRules must come after OrgTextMarkupRule.
444 $RuleOrder{\&LinkRules} = 200;
445 $RuleOrder{\&LineBreakRule} = 210;
447 =head2 OrgGreaterElementsRule
449 =cut
450 sub OrgGreaterElementsRule {
451 # :DRAWER_NAME:
452 # Contents
453 # :END:
454 if (m/\G(\n+|^)([ \t]*:[^\n]+:[ \t]*\n.+?\n[ \t]*:END:[ \t]*(?=(\n|$)))/cgsi) {
455 return '';
458 # #+BEGIN_QUOTE
459 # Contents
460 # #+END_QUOTE
461 if (m/\G(\n+|^)[ \t]*#\+begin_quote[ \t]*?(?=(\n|$))/cgi) {
462 return CloseHtmlEnvironments()
463 .AddHtmlEnvironment('blockquote').AddHtmlEnvironment('p');
466 if (InElement('blockquote') &&
467 m/\G\n[ \t]*#\+end_quote[ \t]*?(?=(\n|$))/cgi) {
468 return CloseHtmlEnvironment('blockquote').AddHtmlEnvironment('p');
471 # A "((...))" footnote anywhere in a page.
473 # Footnotes and the set of all footnotes must be marked so as to ensure their
474 # reevaluation, as each of the footnotes might contain Wiki markup requiring
475 # reevaluation (like, say, free links).
477 # Regular footnotes - [fn:LABEL] DEFINITION
478 if (m/\G((\n+|^)\[(\d+?|fn:.+?)\][ \t]*((.+?)(?=(\n{3,}|\n+\[fn:.+?\]|\n+\*+[ \t]+|$))))/gcos) {
479 Dirty($1); # do not cache the prefixing "\G"
480 my $footnote_label = $3;
481 my $footnote_text = $4;
482 my $footnote_number = FootnoteUpdateRecord($footnote_label, $footnote_text);
483 return '';
486 return undef;
489 =head2 OrgObjectsRule
491 =cut
492 sub OrgObjectsRule {
493 return
494 OrgFootnotesReferenceRule(@_)
495 // OrgLinksRule(@_)
496 // OrgTextMarkupRule(@_)
497 // OrgSubAndSuperscriptRule(@_)
498 // OrgLineBreaksRule(@_)
502 =head2 OrgElementsRule
504 =cut
505 sub OrgElementsRule {
506 # horizontal rule
507 # Four or more dashes.
509 # NOTE: Org insists on 5 or more dashes. wiki.pl uses 4 dashes for
510 # separating out comments in the comments page. In order to get
511 # good looking comment pages on the Wiki, relax the rule a bit here
512 # and admit 4 dashes.
513 if (m/\G(\n+|^)[ \t]*-{4,}[ \t]*(?=(\n|$))/cg) {
514 return CloseHtmlEnvironments().$q->hr().AddHtmlEnvironment('p');
517 # Fixed-width blocks
518 # : line-1
519 # : line-2
520 if (m/\G(\n+|^)((:(\n| [^\n]*))(\n(:(\n| [^\n]*)))*(?=(\n|$)))/cgsi) {
521 my $str = $2;
522 # Strip the leading colon and space from the matched string.
523 $str =~ s/^: ?//msg;
524 return CloseHtmlEnvironments()
525 .$q->pre({-class=> 'real'}, $str)
526 .AddHtmlEnvironment('p');
529 # #+BEGIN_SRC
530 # Source Block
531 # #+END_SRC
532 if (m/\G(\n+|^)([ \t]*)#\+begin_src[ \t]*([^\n]*)\n(.*?)\n\2#\+end_src[ \t]*(?=(\n|$))/cgsi) {
533 my ($prefix, $lang, $lines) = ($2, $3, $4);
534 # Strip leading whitespaces from the source lines.
535 $lines =~ s/^$prefix,?//msg;
536 # Do Syntax Highlighting
537 if (defined(&HighlightSyntax)) {
538 $lines = HighlightSyntax($lines, $OrgLanguageToKateLanguage{$lang});
540 return CloseHtmlEnvironments()
541 .$q->pre({-class=> 'real'}, $lines)
542 .AddHtmlEnvironment('p');
545 # #+BEGIN_EXAMPLE
546 # Example Block
547 # #+END_EXAMPLE
548 if (m/\G(\n+|^)([ \t]*)#\+begin_example[ \t]*[^\n]*\n(.*?)\n\2#\+end_example[ \t]*(?=(\n|$))/cgsi) {
549 my ($prefix, $lines) = ($2, $3);
550 # Strip leading whitespaces and the quoting comma from the example
551 # lines.
552 $lines =~ s/^$prefix,?//msg;
553 return CloseHtmlEnvironments()
554 .$q->pre({-class=> 'real'}, $lines)
555 .AddHtmlEnvironment('p');
558 # Comment and Meta lines - strip those
559 if (m/\G(\n+|^)([ \t]*#\+?[^\n]*)(\n([ \t]*#\+?[^\n]*))*(?=(\n|$))/cgsi) {
560 return '';
563 # 1 or more newlines followed by (optional) spaces
564 if (m/\G(\n+|^)([ ]*)/cg) {
565 my $num_newlines = length($1);
566 my $indentation = length($2);
567 my ($html, $open_new) = MoveUpOrDownList($num_newlines, $indentation);
569 if ($open_new) {
570 $html .= AddHtmlEnvironment('p');
573 return $html if $html;
575 # if we are in a continuation line we might not have added any
576 # markup. Emit a space to account for the newline that we
577 # matched. Return an empty string so that rule matching
578 # progresses.
579 Clean(' ');
580 return '';
583 return undef;
586 =head2 OrgTablesRule
588 =cut
589 sub OrgTablesRule {
590 # Table syntax is matched last (or nearly last), so as to allow
591 # other Org- specific syntax within tables.
593 # tables using | -- end of the table (two newlines) or row (one
594 # newline)
595 if (InElement('table')) {
596 # We know that this is the end of this table row, if we match: *
597 # an explicit "|" character followed by: a newline character and
598 # another "|" character; or * an explicit newline character
599 # followed by: a "|" character.
601 # That is to say, the "|" character terminating a table row is
602 # optional.
604 # In either case, the newline character signifies the end of this
605 # table row and the "|" character that follows it signifies the
606 # start of a new row. We avoid consuming the "|" character by
607 # matching it with a lookahead.
608 if (m/\G([ \t]*\|)?[ \t]*\n(?=$OrgTableCellPattern)/cg) {
609 # Case 1: Horizontal ruler that is followed with a regular table
610 # cell. Start a rowgroup.
611 if (m/\G$OrgTableHrulePattern\n(?=$OrgTableCellPattern)/cg) {
612 return CloseHtmlEnvironmentUntil('table').AddHtmlEnvironment('tbody')
613 .AddHtmlEnvironment('tr');
615 # Case 2: Horizontal ruler that terminates the table. Close the
616 # table.
617 elsif (m/\G$OrgTableHrulePattern(\n|$)/cg) {
618 return CloseHtmlEnvironment('table').AddHtmlEnvironment('p');
620 # Case 3: Regular table row.
621 else {
622 return CloseHtmlEnvironmentUntil('tbody').AddHtmlEnvironment('tr');
625 # If block level elements are allowed in table cells, we know that
626 # this is the end of the table, if we match:
628 # * an explicit "|" character followed by: a newline character
629 # not followed by another "|" character, or an implicit
630 # end-of-page.
632 # Otherwise, we know that this is the end of the table, if we
633 # match:
634 # * an explicit "|" character followed by: a newline character
635 # not followed by another "|" character, or an implicit
636 # end-of-page; or
637 # * two newline characters.
639 # This condition should appear after the end-of-row test, above.
640 elsif (m/\G[ \t]*\|[ \t]*(?=(\n|$))/cg or
641 (!$OrgTableCellsContainBlockLevelElements and m/\G[ \t]*\n\n/cg)) {
642 # Note: we do not call "CloseHtmlEnvironmentsOrgOld", as that
643 # function refers to the Oddmuse built-in. If another
644 # module with name lexically following "org.pl" also
645 # redefines the built-in "CloseHtmlEnvironments" function,
646 # then calling the "CloseHtmlEnvironmentsOrgOld" function
647 # causes that other module's redefinition to not be
648 # called. (Yes; an entangling mess we've made for
649 # ourselves, here. Clearly, this needs a rethink in some
650 # later Oddmuse refactoring.)
651 return CloseHtmlEnvironment('table').AddHtmlEnvironment('p');
653 # Lastly, we know this this is start of a new table cell (and
654 # possibly also the end of the last table cell), if we match: * an
655 # explicit "|" character.
657 # This condition should appear after the end-of-table test, above.
658 elsif (m/\G$OrgTableCellPattern/cg) {
659 # This is the start of a new table cell. However, we only
660 # consider that equivalent to the "$bol_fixme" variable when the
661 # "$OrgTableCellsContainBlockLevelElements" variable is
662 # enabled. (In other words, we only declare that we may insert
663 # block level elements at the start of this new table cell, when
664 # we allow block level elements in table cells. Yum.)
665 $OrgIsTableCellBol = $OrgTableCellsContainBlockLevelElements;
666 my $tag = $2 ? 'th' : 'td';
667 my $attributes;
668 return
669 (InElement('td') || InElement('th') ? CloseHtmlEnvironmentUntil('tr') : '')
670 .AddHtmlEnvironment($tag, $attributes);
673 # tables using | -- an ordinary table cell
675 # Please note that order is important, here; this should appear
676 # after all markup dependent on being in a current table.
678 # Also, the "|" character also signifies the start of a new table
679 # cell. Thus, we avoid consuming that character by matching it with
680 # a lookahead.
681 elsif (m/\G(\n+|^)(($OrgTableHrulePattern\n)?)(?=$OrgTableCellPattern)/cg) {
682 return OpenHtmlEnvironment('table', 1, 'class="user"' . $OrgTableHtmlAttributes)
683 .AddHtmlEnvironment('tbody').AddHtmlEnvironment('tr');
686 return undef;
689 =head2 OrgFootnotesReferenceRule
691 =cut
692 sub OrgFootnotesReferenceRule {
693 # A "((...))" footnote anywhere in a page.
695 # Footnotes and the set of all footnotes must be marked so as to ensure their
696 # reevaluation, as each of the footnotes might contain Wiki markup requiring
697 # reevaluation (like, say, free links).
699 # Anonymous footnotes - [fn::DEFINITION]
700 if (m/\G(\[fn::(.+?)\])/gcos) {
701 Dirty($1); # do not cache the prefixing "\G"
702 my $footnote_text = $2;
703 my $footnote_number = FootnoteUpdateRecord(undef, $footnote_text);
705 printf $FootnoteFormat,
706 $q->a({-href=> '#footnotes'.$footnote_number,
707 -name=> 'footnote' .$footnote_number,
708 -title=> 'Footnote: '. # Truncate link titles to one line.
709 (length($footnote_text) > 48
710 ? substr($footnote_text, 0, 44).'...'
711 : $footnote_text),
712 -class=> 'footnote'
713 }, $footnote_number);
715 return '';
717 # Inline footnotes - [fn:LABEL:DEFINITION]
718 elsif (m/\G(\[(fn:[^]]+?):(.+?)\])/gcos) {
719 Dirty($1); # do not cache the prefixing "\G"
720 my $footnote_label = $2;
721 my $footnote_text = $3;
723 my $footnote_number = FootnoteUpdateRecord($footnote_label, $footnote_text);
725 printf $FootnoteFormat,
726 $q->a({-href=> '#footnotes'.$footnote_number,
727 -name=> 'footnote' .$footnote_number,
728 -title=> 'Footnote: '. # Truncate link titles to one line.
729 (length($footnote_text) > 48
730 ? substr($footnote_text, 0, 44).'...'
731 : $footnote_text),
732 -class=> 'footnote'
733 }, $footnote_number);
735 return '';
737 # Footnote Reference - [fn:LABEL] or [NNN]
738 elsif (m/\G(\[(\d+?|fn:.+?)\])(?=([ \t]*\[fn:(.+?)\])?)/gcos) {
739 Dirty($1); # do not cache the prefixing "\G"
740 my $footnote_label = $2;
741 my $is_adjacent_footnote = defined $3;
742 my $footnote_number = FootnoteUpdateRecord($footnote_label);
744 printf $FootnoteFormat,
745 $q->a({-href=> '#footnotes' .$footnote_number,
746 -name=> 'footnote' .$footnote_number,
747 -title=> 'Footnote #'.$footnote_number,
748 -class=> 'footnote'
749 }, $footnote_number.($is_adjacent_footnote ? ', ' : ''));
751 return '';
754 return undef;
757 =head2 OrgFootnotesSectionRule
759 =cut
760 sub OrgFootnotesSectionRule {
761 # A "((...))" footnote anywhere in a page.
763 # Footnotes and the set of all footnotes must be marked so as to ensure their
764 # reevaluation, as each of the footnotes might contain Wiki markup requiring
765 # reevaluation (like, say, free links).
767 # The "<footnotes>" list of all footnotes at the foot of a page.
768 if ($bol && m/\G($FootnotesPattern)/gcios) {
769 Clean(CloseHtmlEnvironments());
770 Dirty($1); # do not cache the prefixing "\G"
772 if (@FootnoteLabels) {
773 my ($oldpos, $old_) = (pos, $_);
774 PrintFootnotes();
775 Clean(AddHtmlEnvironment('p')); # if dirty block is looked at
776 # later, this will disappear
777 ($_, pos) = ($old_, $oldpos); # restore \G (assignment order matters!)
780 return '';
783 return undef;
786 # ....................{ HTML OUTPUT }....................
787 *PrintFooterFootnotesOld = *PrintFooter;
788 *PrintFooter = *PrintFooterFootnotes;
790 =head2 PrintFooterFootnotes
792 Appends the list of footnotes to the footer of the page, if and only
793 if the user-provided content for that page had no content matching
794 C<$FootersPattern>. Thus, this function is an eleventh-hour fallback;
795 ideally, pages providing footnotes also provide an explicit place to
796 list those footnotes.
798 =cut
799 sub PrintFooterFootnotes {
800 my @params = @_;
801 if (@FootnoteLabels) {
802 PrintFootnotes();
804 PrintFooterFootnotesOld(@params);
807 =head2 PrintFootnotes
809 Prints the list of footnotes.
811 =cut
812 sub PrintFootnotes() {
813 print
814 $q->start_div({-class=> 'footnotes'})
815 .$q->h2(T($FootnotesHeaderText));
817 # Don't use <ol>, because we want to link from the number back to
818 # its page location.
819 foreach my $footnote_label (@FootnoteLabels) {
820 my $footnote_number = $FootnoteRecord{$footnote_label}[0];
821 my $footnote_text = $FootnoteRecord{$footnote_label}[1];
823 print
824 $q->start_div({-class=> 'footnote'})
825 .$q->a({-class=> 'footnote_backlink',
826 -name=> 'footnotes'.$footnote_number,
827 -href=> '#footnote' .$footnote_number}, $footnote_number.'.')
828 .' ';
829 print $q->start_div({-class=> 'footnote_content'});
830 ApplyRules($footnote_text, 1);
831 print $q->end_div();
832 print $q->end_div();
836 print $q->end_div();
838 # Empty the footnotes to disallow our calling the fallback, later.
839 @FootnoteLabels = ();
840 %FootnoteList = {};
841 $FootnoteAnonymousCount = 0;
844 sub FootnoteUpdateRecord {
845 my ($footnote_label, $footnote_text) = @_;
847 # Assign an internally generated label for anonymous footnotes.
848 $footnote_label = (':AnonymousLabel' . ++$FootnoteAnonymousCount)
849 unless defined $footnote_label;
851 if (not exists $FootnoteRecord{$footnote_label}) {
852 # Footnote label is seen for the first time. Assign it a number;
853 push(@FootnoteLabels, $footnote_label);
854 $footnote_number = @FootnoteLabels;
855 $FootnoteRecord{$footnote_label} = [$footnote_number, $footnote_text];
856 } else {
857 # Footnote label already seen. Get it's number;
858 $footnote_number = $FootnoteRecord{$footnote_label}[0];
859 # Note new definition if any.
860 $FootnoteRecord{$footnote_label}[1] = $footnote_text
861 if defined $footnote_text;
864 return $footnote_number;
867 =head2 OrgLinksRule
869 =cut
870 sub OrgLinksRule {
871 # Inline images
872 # - Local image
873 # [[pic.png]]
874 if (m/\G(\[\[($FreeLinkPattern\.$ImageExtensions)\]\])/cgos) {
875 my $text = $2;
876 return GetOrgLinkHtml($1, GetDownloadLink(FreeToNormal($2), 1, undef, $text), $text);
879 # Inline image
880 # - Remote image:
881 # [[url.png]]
882 if (m/\G\[\[($FullUrlPattern$\.$ImageExtensions)\]\]/cgos) {
883 return GetOrgImageHtml(
884 $q->a({-href=> UnquoteHtml($1),
885 -class=> 'image outside'},
886 $q->img({-src=> UnquoteHtml($1),
887 -alt=> UnquoteHtml(''),
888 -class=> 'url outside'})));
891 # Links where description is an image
892 # - Local destination and local image
893 # [[link][pic.png]]
894 if (m/\G(\[\[$FreeLinkPattern$OrgLinkPipePattern
895 ($FreeLinkPattern\.$ImageExtensions)\]\])/cgosx) {
896 my $text = $2;
897 return GetOrgLinkHtml($1, GetOrgImageHtml(
898 ScriptLink(UrlEncode(FreeToNormal($2)),
899 $q->img({-src=> GetDownloadLink(FreeToNormal($3), 2),
900 -alt=> UnquoteHtml($text),
901 -class=> 'upload'}), 'image')), $text);
904 # - Local destination and remote image
905 # [[link][url.png]]
906 if (m/\G(\[\[$FreeLinkPattern$OrgLinkPipePattern
907 ($FullUrlPattern\.$ImageExtensions)\]\])/cgosx) {
908 my $text = $2;
909 return GetOrgLinkHtml($1, GetOrgImageHtml(
910 ScriptLink(UrlEncode(FreeToNormal($2)),
911 $q->img({-src=> UnquoteHtml($3),
912 -alt=> UnquoteHtml($text),
913 -class=> 'url outside'}), 'image')), $text);
916 # - Remote destination and local image
917 # [[url][pic.png]]
918 if (m/\G(\[\[$FullUrlPattern$OrgLinkPipePattern
919 ($FreeLinkPattern\.$ImageExtensions)\]\])/cgosx) {
920 my $text = $2;
921 return GetOrgLinkHtml($1, GetOrgImageHtml(
922 $q->a({-href=> UnquoteHtml($2), -class=> 'image outside'},
923 $q->img({-src=> GetDownloadLink(FreeToNormal($3), 2),
924 -alt=> UnquoteHtml($text),
925 -class=> 'upload'}))), $text);
928 # - Remote destination and remote image
929 # [[url][url.png]]
930 if (m/\G\[\[$FullUrlPattern$OrgLinkPipePattern
931 ($FullUrlPattern\.$ImageExtensions)\]\]/cgosx) {
932 return GetOrgImageHtml(
933 $q->a({-href=> UnquoteHtml($1), -class=> 'image outside'},
934 $q->img({-src=> UnquoteHtml($2),
935 -alt=> UnquoteHtml(''),
936 -class=> 'url outside'})));
939 # link: [[url]] and [[url][text]]
940 if (m/\G\[\[$FullUrlPattern$OrgLinkTextPattern\]\]/cgos) {
941 # Permit embedding of Org syntax within link text. (Rather
942 # complicated, but it does the job remarkably.)
943 my $link_url = $1;
944 my $link_text = $3 ? OrgTextMarkupRuleRecursive($3, @_) : $link_url;
946 # GetUrl() takes parameters resembling:
947 # ~ the link's URL.
948 # ~ the link's text (to be displayed for that URL).
949 # ~ a boolean (to be used Gods' know how).
950 return GetUrl($link_url, $link_text, 1);
953 # link: [[page]] and [[page][text]]
954 if (m/\G(\[\[$FreeLinkPattern$OrgLinkTextPattern\]\])/cgos) {
955 my $markup = $1;
956 my $page_name = $2;
957 my $link_text = $4 ? OrgTextMarkupRuleRecursive($4, @_) : $page_name;
959 return GetOrgLinkHtml($markup,
960 GetPageOrEditLink($page_name, $link_text, 0, 1), $link_text);
963 # interlink: [[Wiki:page]] and [[Wiki:page][text]]
964 if ($is_interlinking and
965 m/\G(\[\[$FreeInterLinkPattern$OrgLinkTextPattern\]\])/cgos) {
966 my $markup = $1;
967 my $interlink = $2;
968 my $interlink_text = $4;
969 my ($site_name, $page_name) = $interlink =~ m~^($InterSitePattern):(.*)$~;
971 # Permit embedding of Org syntax within interlink text. We operate
972 # on "$interlink_text", rather than "$4", since that ordinal has
973 # already been overridden by the above regular expression match.
974 $interlink_text = $interlink_text
975 ? OrgTextMarkupRuleRecursive($interlink_text, @_)
976 : $q->span({-class=> 'site'}, $site_name)
977 .$q->span({-class=> 'separator'}, ':')
978 .$q->span({-class=> 'page'}, $page_name);
980 # If the Wiki for this interlink is a registered Wiki (that is, it
981 # appears in this Wiki's "$InterMap" page), then produce an
982 # interlink to it; otherwise, produce a normal intralink to a page
983 # on this Wiki.
984 return GetOrgLinkHtml($markup,
985 $InterSite{$site_name}
986 ? GetInterLink ($interlink, $interlink_text, 0, 1)
987 : GetPageOrEditLink($page_name, $interlink_text, 0, 1), $interlink_text);
990 return undef;
993 =head2 OrgTextMarkupRule
995 Handles the large part of Wiki Org syntax.
997 Technically, as Oddmuse's default C<LinkRules> function also conflicts
998 with this extension's link rules and does not comply, in any case,
999 with the Wiki Org rules for links, we should also nullify Oddmuse's
1000 default C<LinkRules> function. Sadly, we don't. Why? Since existing
1001 Oddmuse Wikis using this extension depend on Oddmuse's default
1002 C<LinkRules> function, and as it's no terrible harm to let that
1003 function be, we have to let it be. Bah!
1005 =cut
1006 sub OrgTextMarkupRule {
1007 # "$is_interlinking" is a boolean that, if true, indicates this rule
1008 # should make interlinks (i.e., links to Wiki pages on other,
1009 # external Wikis) and, and, if false, should not. (Typically,
1010 # Oddmuse sets this to false when including external HTML pages into
1011 # local Wiki pages.)
1012 my ($is_interlinking, $is_intraanchoring) = @_;
1014 if (m!\G(\*\*|//|__|\+\+|~~|==)!cgos) {
1015 my $marker = $1;
1016 my $html_tag = $OrgEmphasisAlist{$marker};
1018 return AddOrCloseHtmlEnvironment($html_tag);
1021 # Text markup - Bold, Italic, Underline, Verbatim, Monospace and
1022 # Strikethrough.
1024 if (m!\G([*/_+~=])!cgos) {
1025 my $pre = $-[0] ? substr($_, $-[0]-1, 1) : '';
1026 my $post = substr($_, $+[0], 1);
1027 my $marker = $1;
1028 my $html_tag = $OrgEmphasisAlist{$marker};
1030 if (InElement($html_tag)) {
1031 # End marker should be bound by non-whitespace on the left and
1032 # whitespace or punctuation character on the right.
1033 return CloseHtmlEnvironment($html_tag)
1034 if ($pre and $pre !~ /[\ \t\r\n,'\"]/ and
1035 (not $post or $post =~ /[-\ \t.,:?;'\")\n*\/_+=~]/));
1036 } else {
1037 return AddHtmlEnvironment($html_tag)
1038 # Start marker should be bound by whitespace or punctuation
1039 # character on the left and non-whitespace character on the
1040 # right.
1041 if ((not $pre or $pre =~ /[\ \t('\"{\n*\/_+=~]/)
1042 and $post !~ /[\ \t\r\n,'\"]/) ;
1045 return $marker;
1048 return undef;
1051 =head2 OrgSubAndSuperscriptRule
1053 =cut
1054 sub OrgSubAndSuperscriptRule {
1055 # Superscript: ^^sup^^
1056 if ($OrgAdditionSupSub and m/\G\^\^/cg) {
1057 return AddOrCloseHtmlEnvironment('sup');
1060 # Subscript: ,,sub,,
1061 if ($OrgAdditionSupSub and m/\G\,\,/cg) {
1062 return AddOrCloseHtmlEnvironment('sub');
1065 return undef;
1068 =head2 OrgLineBreaksRule
1070 =cut
1071 sub OrgLineBreaksRule {
1073 # Trailing whitespace
1074 if (m/\G[ \t]*(?=(\n|$))/cg) {
1075 return $OrgLineBreaks ? $q->br() : '';
1078 # Explicit linebreak
1079 if (m/\G\\\\[ \t]*(?=(\n|$))/cg) {
1080 return $q->br();
1083 return undef;
1086 sub OrgHeadingRule {
1087 # header opening: = to ====== for h1 to h6
1089 # header opening and closing have been partitioned into two separate
1090 # conditional matches rather than congealed into one conditional
1091 # match. Why? Because, in so doing, we permit application of other
1092 # markup rules, elsewhere, to header text. This, in turn, permits
1093 # insertion and interpretation of complex markup in header text;
1094 # e.g., ** /This Is a *Level-2* Header Having Complex Markup./
1095 if (m~\G(^|\n)+(\*+)[ \t]+~cg) {
1096 my $header_depth = length($2);
1097 ($OrgHeaderHtmlTag, $OrgHeaderHtmlTagAttr) = $header_depth <= 6
1098 ? ('h'.$header_depth, '')
1099 : ('h6', qq{class="h$header_depth"});
1100 return CloseHtmlEnvironments()
1101 . AddHtmlEnvironment($OrgHeaderHtmlTag, $OrgHeaderHtmlTagAttr);
1103 # header closing: = to ======, newline, or EOF
1105 # Note: partitioning this from the heading opening conditional,
1106 # above, typically causes Oddmuse to insert an extraneous space at
1107 # the end of header tags. This is non-dangerous, fortunately; and
1108 # changes nothing.
1109 elsif ($OrgHeaderHtmlTag and m~\G[ \t]*(?=(\n|$))~cg) {
1110 my $header_html =
1111 CloseHtmlEnvironment($OrgHeaderHtmlTag, '^'.$OrgHeaderHtmlTagAttr.'$')
1112 .AddHtmlEnvironment('p');
1113 $OrgHeaderHtmlTag = $OrgHeaderHtmlTagAttr = '';
1114 return $header_html;
1117 return undef;
1120 sub GetParentIndentation {
1121 my $from = shift;
1122 my $i = $from // -1;
1123 my $maxIndex = $#HtmlStack;
1125 # Search searching the stack from an index one higher than the
1126 # passed in value or from zero.
1127 while (++$i <= $maxIndex) {
1128 my $current_indentation =
1129 GetStashedAttributeValue($HtmlAttrStack[$i], '_indentation_');
1131 # Return a list of index and current indentation.
1132 return ($i, $current_indentation)
1133 if (defined ($current_indentation));
1136 # No parent indentation is found.
1137 return (-1, -1);
1140 sub GetSiblingOrParent {
1141 my $indentation = shift;
1142 my $maxIndex = $#HtmlStack;
1143 my $i = -1;
1144 my $current_indentation = -1;
1146 while ($i <= $maxIndex) {
1147 ($i, $current_indentation) = GetParentIndentation($i);
1148 # No more parents.
1149 last if ($i eq -1);
1151 # Found a level that is at the same level as or is the parent
1152 # of the requested indentation. Return it.
1153 return ($i, $current_indentation)
1154 if ($current_indentation <= $indentation);
1157 # No parent level exists.
1158 return (-1, -1);
1161 sub SwitchToIndentation {
1162 my ($indentation) = @_;
1163 my $html = '';
1165 # If we are switching to level-0, there is nothing much to do.
1166 # Close all open environments and return.
1167 return CloseHtmlEnvironments()
1168 unless $indentation;
1170 # Get sibling or parent.
1171 my ($i, $current_indentation) = GetSiblingOrParent($indentation);
1173 # None found i.e., Leaving the current list context.
1174 if ($i eq -1) {
1175 # Close everything and return.
1176 $html .= CloseHtmlEnvironments();
1177 return $html;
1180 # Found a Sibling or a Parent.
1182 # Set index to the list item i.e., Leave the 'li' tag on the stack
1183 # at the exit of this subroutine.
1184 --$i;
1186 # Save the parent context.
1187 my @stack = splice(@HtmlStack, $i);
1188 my @attrstack = splice(@HtmlAttrStack, $i);
1190 # Close everything contained within 'li'.
1191 my $html = CloseHtmlEnvironments();
1193 # Restore the stack context.
1194 @HtmlStack = @stack;
1195 @HtmlAttrStack = @attrstack;
1197 return $html;
1200 sub OpenHtmlEnvironmentUseIndentation {
1201 my ($html_tag, $indentation, $html_tag_attr) = @_;
1203 my $html = SwitchToIndentation($indentation);
1204 my $current_indentation = GetParentIndentation();
1206 if ($current_indentation != -1 and
1207 $current_indentation eq $indentation) {
1208 # Sibling context. Close the list item. This leave 'ol' or
1209 # 'ul' on the stack.
1210 return $html .= CloseHtmlTag(shift(@HtmlStack), shift(@HtmlAttrStack));
1213 # FIXME:
1214 # $depth = $IndentLimit if $depth > $IndentLimit; # requested depth
1215 # # 0 makes no sense
1217 # A new top-level list or a sublist. Open a 'ul' or 'ul'.
1219 # backwards-compatibility hack: classically, the third argument to
1220 # this function was a single CSS class, rather than string of HTML
1221 # tag attributes as in the second argument to the
1222 # "AddHtmlEnvironment" function. To allow both sorts, we
1223 # conditionally change this string to 'class="$html_tag_attr"'
1224 # when this string is a single CSS class.
1225 $html_tag_attr = qq/class="$html_tag_attr"/
1226 if $html_tag_attr && $html_tag_attr !~ m/^\s*.+?\s*=\s*('|").+\1/;
1228 # Stash the current indentation level within the tag
1229 $html_tag_attr =
1230 StashAttributeValue($html_tag_attr, '_indentation_', $indentation);
1232 # Open the new tag.
1233 $html .= AddHtmlEnvironment($html_tag, $html_tag_attr);
1235 return $html;
1238 =head2 OrgListRule
1240 =cut
1241 sub OrgListRule {
1242 my $html = '';
1244 # 1 or more newlines followed by (optional) spaces and (optional)
1245 # list bullet.
1246 if (m/\G(\n+|^)([ ]*)((\d+.|[-+*]) )/cg) {
1247 my $num_newlines = length($1);
1248 my $indentation = length($2);
1249 my $bullet = $4;
1251 if ($num_newlines > 2) {
1252 # Three or more newlines
1253 $html .= CloseHtmlEnvironments();
1256 # Found a list item.
1258 # List items are considered to be indented to a level one
1259 # greater than the leading whitespace. Thus, they are
1260 # always indented by atleast 1.
1261 ++$indentation;
1263 # Ordered or Un-ordered?
1264 my $html_tag = ($bullet =~ /[-+*]/) ? 'ul' : 'ol';
1266 $html .=
1267 OpenHtmlEnvironmentUseIndentation($html_tag, $indentation)
1268 .AddHtmlEnvironment ('li');
1270 return $html;
1273 return undef;
1276 sub MoveUpOrDownList {
1277 my ($num_newlines, $indentation) = @_;
1278 my $html = '';
1280 # 1 or more newlines followed by (optional) spaces.
1281 if ($num_newlines > 2) {
1282 # Three or more newlines
1283 $html .= CloseHtmlEnvironments();
1286 # Found an indented line. May or may not be a paragraph.
1288 # Regular lines are considered to be indented to the same level as
1289 # the leading whitespace. Thus, their indentation is greater than
1290 # or equal to zero.
1291 my $parent_indentation = GetParentIndentation();
1292 my $current_indentation = ($parent_indentation == -1) ? 0
1293 : $parent_indentation;
1295 # Force lines that are more indented than the current level to
1296 # be at the same level as the current level.
1297 if ($indentation > $current_indentation) {
1298 $indentation = $current_indentation;
1301 my $open_new = !$current_indentation # outside of a list
1302 || ($indentation < $current_indentation) # within a list, but leaving it
1303 || ($num_newlines > 1) # staying within the
1304 # list, but two new
1305 # lines seen.
1308 if ($indentation < $current_indentation or $num_newlines > 1) {
1309 $html .= SwitchToIndentation($indentation);
1312 return ($html, $open_new);
1315 # ....................{ HTML }....................
1317 =head2 GetOrgImageHtml
1319 Returns the passed HTML image, conditionally wrapped within an HTML
1320 paragraph tag having an necessary image class when the passed HTML
1321 also represents such a new paragraph. Difficult to explain, isn't she?
1323 =cut
1324 sub GetOrgImageHtml {
1325 my $image_html = shift;
1326 my $bol_fixme = (m/\G(^|(?<=\n))/cg);
1328 return
1329 ($bol_fixme ? CloseHtmlEnvironments().AddHtmlEnvironment('p', 'class="image"') : '')
1330 .$image_html;
1333 =head2 GetOrgLinkHtml
1335 Marks the passed HTML as a dirty block, unless this HTML belongs to an
1336 HTML header tag. Such tags may not contain dirty blocks! Most Oddmuse
1337 modules using header tags (e.g., "sidebar.pl", "toc.pl") require, as a
1338 caching efficiency, header text to be clean. This is a nearly
1339 necessary efficiency, since regeneration of markup for those modules
1340 is an often costly operation. (We certainly don't want to regenerate
1341 the Table of Contents for each page having at least one header having
1342 at least one dirty link whenever an external user browses to that
1343 page!)
1345 Thus, if in a header, this function cleans links out of the passed
1346 HTML and returns the resultant HTML (to the current clean
1347 block). Otherwise, this function appends the resultant HTML to a new
1348 dirty block, prints it, and returns it. (This does not print the
1349 resultant HTML when clean, since clean blocks are printed,
1350 automatically, by the next call to C<Dirty>.)
1352 This function, lastly, accepts three function parameters. These are:
1354 =over
1356 =item C<$markup>. (This is the Wiki markup string to be marked as
1357 dirty when it is not embedded in a Org header.)
1359 =item C<$html>. (This is the HTML string to be marked as dirty when
1360 this HTML is not embedded in a Org header.)
1362 =item C<$text>. (This is the text string to be marked as clean when
1363 this HTML is embedded within a Org header.)
1365 =back
1367 Org functions, above, should **not** call the C<Dirty> function
1368 directly. Rather, they should always call this function...with
1369 appropriate parameters.
1371 =cut
1372 sub GetOrgLinkHtml {
1373 my ($markup, $html, $link_text) = @_;
1375 if ($OrgHeaderHtmlTag) { return $link_text; }
1376 else {
1377 Dirty($markup);
1378 print $html;
1379 return '';
1383 # ....................{ FUNCTIONS }....................
1384 *RunMyRulesOrgOld = *RunMyRules;
1385 *RunMyRules = *RunMyRulesOrg;
1387 =head2 RunMyRulesOrg
1389 Runs all markup rules for the current block of page markup. This
1390 redefinition ensures that the beginning of a table cell is considered
1391 the beginning of a block-level element -- that, in other words, the
1392 C<$bol_fixme> global be set to 1.
1394 If the C<$OrgTableCellsContainBlockLevelElements> option is set to 0
1395 (the default), then this function is, effectively, a no-op - and just
1396 calls the default C<RunMyRules> function.
1398 =cut
1399 sub RunMyRulesOrg {
1400 # See documentation for the "$OrgIsTableCellBol" variable, above.
1401 my $org_is_table_cell_bol_last = $OrgIsTableCellBol;
1402 $bol_fixme = 1 if $OrgIsTableCellBol;
1403 my $html = RunMyRulesOrgOld(@_);
1404 $OrgIsTableCellBol = '' if $org_is_table_cell_bol_last;
1406 return $html;
1409 =head2 OrgTextMarkupRuleRecursive
1411 Calls C<OrgTextMarkupRule> on the passed string, from within some
1412 existing call to C<OrgTextMarkupRule>. This function ensures, among
1413 other safeties, that the C<OrgTextMarkupRule> function is not recursed
1414 into more than once.
1416 =cut
1417 sub OrgTextMarkupRuleRecursive {
1418 my $markup = shift;
1419 return $markup if $OrgTextMarkupRuleRecursing; # avoid infinite loops
1420 local $OrgTextMarkupRuleRecursing = 1;
1421 local $bol_fixme = 0; # prevent block level element
1422 # handling
1424 # Preserve global variables.
1425 my ($oldpos, $old_) = (pos, $_);
1426 my @oldHtmlStack = @HtmlStack;
1427 my @oldHtmlAttrStack = @HtmlAttrStack;
1429 # Reset global variables.
1430 $_ = $markup;
1431 @HtmlStack = @HtmlAttrStack = ();
1433 my ($html, $html_org) = ('', '');
1435 # The contents of this loop are, in part, hacked from the guts of
1436 # Oddmuse's ApplyRules() function. We cannot simply call that
1437 # function, as it "cleans" the HTML converted from the text passed
1438 # to it, rather than returns that HTML.
1439 while (1) {
1440 if ($html_org = OrgTextMarkupRule(@_) or
1441 ($OrgIsOrgAddition and # try "orgaddition.pl", too.
1442 $html_org = OrgAdditionRule(@_))) {
1443 $html .= $html_org;
1445 elsif (m/\G&amp;([a-z]+|#[0-9]+|#x[a-fA-F0-9]+);/cg) { # entity references
1446 $html .= "&$1;";
1448 elsif (m/\G\s+/cg) {
1449 $html .= ' ';
1451 elsif ( m/\G([A-Za-z\x{0080}-\x{fffd}]+([ \t]+[a-z\x{0080}-\x{fffd}]+)*[ \t]+)/cg
1452 or m/\G([A-Za-z\x{0080}-\x{fffd}]+)/cg
1453 or m/\G(\S)/cg) {
1454 $html .= $1; # multiple words but do not match http://foo
1456 else { last; }
1459 # Restore global variables, in reverse order.
1460 @HtmlAttrStack = @oldHtmlAttrStack;
1461 @HtmlStack = @oldHtmlStack;
1462 ($_, pos) = ($old_, $oldpos);
1464 # Allow entrance into this function, again.
1465 $OrgTextMarkupRuleRecursing = 0;
1467 return $html;
1470 =head1 COPYRIGHT AND LICENSE
1472 Copyright (C) 2004, 2006-7 Alex Schroeder <alex@gnu.org>;
1473 Copyleft (C) 2008 Brian Curry <http://raiazome.com>;
1474 Copyright (C) 2008 Weakish Jiang <weakish@gmail.com>;
1475 Copyright (C) 2013 Jambunathan K <kjambunathan@gmail.com>
1477 This program is free software; you can redistribute it and/or modify
1478 it under the terms of the GNU General Public License as published by
1479 the Free Software Foundation; either version 3 of the License, or
1480 (at your option) any later version.
1482 This program is distributed in the hope that it will be useful,
1483 but WITHOUT ANY WARRANTY; without even the implied warranty of
1484 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1485 GNU General Public License for more details.
1487 You should have received a copy of the GNU General Public License
1488 along with this program. If not, see L<http://www.gnu.org/licenses/>.
1490 =cut