1####################################################################################################################################
2# DOC RENDER MODULE
3####################################################################################################################################
4package pgBackRestDoc::Common::DocRender;
5
6use strict;
7use warnings FATAL => qw(all);
8use Carp qw(confess);
9
10use Exporter qw(import);
11    our @EXPORT = qw();
12use JSON::PP;
13use Storable qw(dclone);
14
15use pgBackRestDoc::Common::DocManifest;
16use pgBackRestDoc::Common::Log;
17use pgBackRestDoc::Common::String;
18
19####################################################################################################################################
20# XML tag/param constants
21####################################################################################################################################
22use constant XML_SECTION_PARAM_ANCHOR                               => 'anchor';
23    push @EXPORT, qw(XML_SECTION_PARAM_ANCHOR);
24use constant XML_SECTION_PARAM_ANCHOR_VALUE_NOINHERIT               => 'no-inherit';
25    push @EXPORT, qw(XML_SECTION_PARAM_ANCHOR_VALUE_NOINHERIT);
26
27####################################################################################################################################
28# Render tags for various output types
29####################################################################################################################################
30my $oRenderTag =
31{
32    'markdown' =>
33    {
34        'quote' => ['"', '"'],
35        'b' => ['**', '**'],
36        'i' => ['_', '_'],
37        # 'bi' => ['_**', '**_'],
38        'ul' => ["\n", ""],
39        'ol' => ["\n", "\n"],
40        'li' => ['- ', "\n"],
41        'id' => ['`', '`'],
42        'file' => ['`', '`'],
43        'path' => ['`', '`'],
44        'cmd' => ['`', '`'],
45        'param' => ['`', '`'],
46        'setting' => ['`', '`'],
47        'pg-setting' => ['`', '`'],
48        'code' => ['`', '`'],
49        # 'code-block' => ['```', '```'],
50        # 'exe' => [undef, ''],
51        'backrest' => [undef, ''],
52        'proper' => ['', ''],
53        'postgres' => ['PostgreSQL', ''],
54        'admonition' => ["\n> **", "\n"],
55    },
56
57    'text' =>
58    {
59        'quote' => ['"', '"'],
60        'b' => ['', ''],
61        'i' => ['', ''],
62        # 'bi' => ['', ''],
63        'ul' => ["\n", "\n"],
64        'ol' => ["\n", "\n"],
65        'li' => ['* ', "\n"],
66        'id' => ['', ''],
67        'host' => ['', ''],
68        'file' => ['', ''],
69        'path' => ['', ''],
70        'cmd' => ['', ''],
71        'br-option' => ['', ''],
72        'pg-setting' => ['', ''],
73        'param' => ['', ''],
74        'setting' => ['', ''],
75        'code' => ['', ''],
76        'code-block' => ['', ''],
77        'exe' => [undef, ''],
78        'backrest' => [undef, ''],
79        'proper' => ['', ''],
80        'postgres' => ['PostgreSQL', ''],
81        'admonition' => ["\n", "\n\n"],
82    },
83
84    'latex' =>
85    {
86        'quote' => ['``', '"'],
87        'b' => ['\textbf{', '}'],
88        'i' => ['\textit{', '}'],
89        # 'bi' => ['', ''],
90        'ul' => ["\\begin{itemize}\n", "\\end{itemize}\n"],
91        # 'ol' => ["\n", "\n"],
92        'li' => ['\item ', "\n"],
93        'id' => ['\textnormal{\texttt{', '}}'],
94        'host' => ['\textnormal{\textbf{', '}}'],
95        'file' => ['\textnormal{\texttt{', '}}'],
96        'path' => ['\textnormal{\texttt{', '}}'],
97        'cmd' => ['\textnormal{\texttt{', "}}"],
98        'user' => ['\textnormal{\texttt{', '}}'],
99        'br-option' => ['', ''],
100        # 'param' => ['\texttt{', '}'],
101        # 'setting' => ['\texttt{', '}'],
102        'br-option' => ['\textnormal{\texttt{', '}}'],
103        'br-setting' => ['\textnormal{\texttt{', '}}'],
104        'pg-option' => ['\textnormal{\texttt{', '}}'],
105        'pg-setting' => ['\textnormal{\texttt{', '}}'],
106        'code' => ['\textnormal{\texttt{', '}}'],
107        # 'code' => ['\texttt{', '}'],
108        # 'code-block' => ['', ''],
109        # 'exe' => [undef, ''],
110        'backrest' => [undef, ''],
111        'proper' => ['\textnormal{\texttt{', '}}'],
112        'postgres' => ['PostgreSQL', ''],
113        'admonition' =>
114            ["\n\\vspace{.5em}\\begin{leftbar}\n\\begin{sloppypar}\\textit{\\textbf{", "}\\end{sloppypar}\n\\end{leftbar}\n"],
115    },
116
117    'html' =>
118    {
119        'quote' => ['<q>', '</q>'],
120        'b' => ['<b>', '</b>'],
121        'i' => ['<i>', '</i>'],
122        # 'bi' => ['<i><b>', '</b></i>'],
123        'ul' => ['<ul>', '</ul>'],
124        'ol' => ['<ol>', '</ol>'],
125        'li' => ['<li>', '</li>'],
126        'id' => ['<span class="id">', '</span>'],
127        'host' => ['<span class="host">', '</span>'],
128        'file' => ['<span class="file">', '</span>'],
129        'path' => ['<span class="path">', '</span>'],
130        'cmd' => ['<span class="cmd">', '</span>'],
131        'user' => ['<span class="user">', '</span>'],
132        'br-option' => ['<span class="br-option">', '</span>'],
133        'br-setting' => ['<span class="br-setting">', '</span>'],
134        'pg-option' => ['<span class="pg-option">', '</span>'],
135        'pg-setting' => ['<span class="pg-setting">', '</span>'],
136        'code' => ['<span class="id">', '</span>'],
137        'code-block' => ['<code-block>', '</code-block>'],
138        'exe' => [undef, ''],
139        'setting' => ['<span class="br-setting">', '</span>'], # ??? This will need to be fixed
140        'backrest' => [undef, ''],
141        'proper' => ['<span class="host">', '</span>'],
142        'postgres' => ['<span class="postgres">PostgreSQL</span>', ''],
143        'admonition' => ['<div class="admonition">', '</div>'],
144    }
145};
146
147####################################################################################################################################
148# CONSTRUCTOR
149####################################################################################################################################
150sub new
151{
152    my $class = shift;       # Class name
153
154    # Create the class hash
155    my $self = {};
156    bless $self, $class;
157
158    # Assign function parameters, defaults, and log debug info
159    (
160        my $strOperation,
161        $self->{strType},
162        $self->{oManifest},
163        $self->{bExe},
164        $self->{strRenderOutKey},
165    ) =
166        logDebugParam
167        (
168            __PACKAGE__ . '->new', \@_,
169            {name => 'strType'},
170            {name => 'oManifest', required => false},
171            {name => 'bExe', required => false},
172            {name => 'strRenderOutKey', required => false}
173        );
174
175    # Create JSON object
176    $self->{oJSON} = JSON::PP->new()->allow_nonref();
177
178    # Initialize project tags
179    $$oRenderTag{markdown}{backrest}[0] = "{[project]}";
180    $$oRenderTag{markdown}{exe}[0] = "{[project-exe]}";
181
182    $$oRenderTag{text}{backrest}[0] = "{[project]}";
183    $$oRenderTag{text}{exe}[0] = "{[project-exe]}";
184
185    $$oRenderTag{latex}{backrest}[0] = "{[project]}";
186    $$oRenderTag{latex}{exe}[0] = "\\textnormal\{\\texttt\{[project-exe]}}\}\}";
187
188    $$oRenderTag{html}{backrest}[0] = "<span class=\"backrest\">{[project]}</span>";
189    $$oRenderTag{html}{exe}[0] = "<span class=\"file\">{[project-exe]}</span>";
190
191    if (defined($self->{strRenderOutKey}))
192    {
193        # Copy page data to self
194        my $oRenderOut =
195            $self->{oManifest}->renderOutGet($self->{strType} eq 'latex' ? 'pdf' : $self->{strType}, $self->{strRenderOutKey});
196
197        # If these are the backrest docs then load the reference
198        if ($self->{oManifest}->isBackRest())
199        {
200            $self->{oReference} =
201                new pgBackRestDoc::Common::DocConfig(${$self->{oManifest}->sourceGet('reference')}{doc}, $self);
202        }
203
204        if (defined($$oRenderOut{source}) && $$oRenderOut{source} eq 'reference' && $self->{oManifest}->isBackRest())
205        {
206            if ($self->{strRenderOutKey} eq 'configuration')
207            {
208                $self->{oDoc} = $self->{oReference}->helpConfigDocGet();
209            }
210            elsif ($self->{strRenderOutKey} eq 'command')
211            {
212                $self->{oDoc} = $self->{oReference}->helpCommandDocGet();
213            }
214            else
215            {
216                confess &log(ERROR, "cannot render $self->{strRenderOutKey} from source $$oRenderOut{source}");
217            }
218        }
219        elsif (defined($$oRenderOut{source}) && $$oRenderOut{source} eq 'release' && $self->{oManifest}->isBackRest())
220        {
221            require pgBackRestDoc::Custom::DocCustomRelease;
222            pgBackRestDoc::Custom::DocCustomRelease->import();
223
224            $self->{oDoc} =
225                (new pgBackRestDoc::Custom::DocCustomRelease(
226                    ${$self->{oManifest}->sourceGet('release')}{doc},
227                    defined($self->{oManifest}->variableGet('dev')) && $self->{oManifest}->variableGet('dev') eq 'y'))->docGet();
228        }
229        else
230        {
231            $self->{oDoc} = ${$self->{oManifest}->sourceGet($self->{strRenderOutKey})}{doc};
232        }
233
234        $self->{oSource} = $self->{oManifest}->sourceGet($$oRenderOut{source});
235    }
236
237    if (defined($self->{strRenderOutKey}))
238    {
239        # Build the doc
240        $self->build($self->{oDoc});
241
242        # Get required sections
243        foreach my $strPath (@{$self->{oManifest}->{stryRequire}})
244        {
245            if (substr($strPath, 0, 1) ne '/')
246            {
247                confess &log(ERROR, "path ${strPath} must begin with a /");
248            }
249
250            if (!defined($self->{oSection}->{$strPath}))
251            {
252                confess &log(ERROR, "required section '${strPath}' does not exist");
253            }
254
255            if (defined(${$self->{oSection}}{$strPath}))
256            {
257                $self->required($strPath);
258            }
259        }
260    }
261
262    if (defined($self->{oDoc}))
263    {
264        $self->{bToc} = !defined($self->{oDoc}->paramGet('toc', false)) || $self->{oDoc}->paramGet('toc') eq 'y' ? true : false;
265        $self->{bTocNumber} =
266            $self->{bToc} &&
267            (!defined($self->{oDoc}->paramGet('toc-number', false)) || $self->{oDoc}->paramGet('toc-number') eq 'y') ? true : false;
268    }
269
270    # Return from function and log return values if any
271    return logDebugReturn
272    (
273        $strOperation,
274        {name => 'self', value => $self}
275    );
276}
277
278####################################################################################################################################
279# Set begin and end values for a tag
280####################################################################################################################################
281sub tagSet
282{
283    my $self = shift;
284    my $strTag = shift;
285    my $strBegin = shift;
286    my $strEnd = shift;
287
288    $oRenderTag->{$self->{strType}}{$strTag}[0] = defined($strBegin) ? $strBegin : '';
289    $oRenderTag->{$self->{strType}}{$strTag}[1] = defined($strEnd) ? $strEnd : '';
290}
291
292####################################################################################################################################
293# variableReplace
294#
295# Replace variables in the string.
296####################################################################################################################################
297sub variableReplace
298{
299    my $self = shift;
300
301    return defined($self->{oManifest}) ? $self->{oManifest}->variableReplace(shift, $self->{strType}) : shift;
302}
303
304####################################################################################################################################
305# variableSet
306#
307# Set a variable to be replaced later.
308####################################################################################################################################
309sub variableSet
310{
311    my $self = shift;
312
313    return $self->{oManifest}->variableSet(shift, shift);
314}
315
316####################################################################################################################################
317# variableGet
318#
319# Get the current value of a variable.
320####################################################################################################################################
321sub variableGet
322{
323    my $self = shift;
324
325    return $self->{oManifest}->variableGet(shift);
326}
327
328####################################################################################################################################
329# Get pre-execute list for a host
330####################################################################################################################################
331sub preExecute
332{
333    my $self = shift;
334    my $strHost = shift;
335
336    if (defined($self->{preExecute}{$strHost}))
337    {
338        return @{$self->{preExecute}{$strHost}};
339    }
340
341    return;
342}
343
344####################################################################################################################################
345# build
346#
347# Build the section map and perform filtering.
348####################################################################################################################################
349sub build
350{
351    my $self = shift;
352    my $oNode = shift;
353    my $oParent = shift;
354    my $strPath = shift;
355    my $strPathPrefix = shift;
356
357    # &log(INFO, "        node " . $oNode->nameGet());
358
359    my $strName = $oNode->nameGet();
360
361    if (defined($oParent))
362    {
363        # Evaluate if condition -- when false the node will be removed
364        if (!$self->{oManifest}->evaluateIf($oNode))
365        {
366            my $strDescription;
367
368            if (defined($oNode->nodeGet('title', false)))
369            {
370                $strDescription = $self->processText($oNode->nodeGet('title')->textGet());
371            }
372
373            &log(DEBUG, "            filtered ${strName}" . (defined($strDescription) ? ": ${strDescription}" : ''));
374
375            $oParent->nodeRemove($oNode);
376            return;
377        }
378    }
379    else
380    {
381            &log(DEBUG, '        build document');
382            $self->{oSection} = {};
383    }
384
385    # Build section
386    if ($strName eq 'section')
387    {
388        my $strSectionId = $oNode->paramGet('id');
389        &log(DEBUG, "build section [${strSectionId}]");
390
391        # Set path and parent-path for this section
392        if (defined($strPath))
393        {
394            $oNode->paramSet('path-parent', $strPath);
395        }
396
397        $strPath .= '/' . $oNode->paramGet('id');
398
399        &log(DEBUG, "            path ${strPath}");
400        ${$self->{oSection}}{$strPath} = $oNode;
401        $oNode->paramSet('path', $strPath);
402
403        # If depend is not set then set it to the last section
404        my $strDepend = $oNode->paramGet('depend', false);
405
406        my $oContainerNode = defined($oParent) ? $oParent : $self->{oDoc};
407        my $oLastChild;
408        my $strDependPrev;
409
410        foreach my $oChild ($oContainerNode->nodeList('section', false))
411        {
412            if ($oChild->paramGet('id') eq $oNode->paramGet('id'))
413            {
414                if (defined($oLastChild))
415                {
416                    $strDependPrev = $oLastChild->paramGet('id');
417                }
418                elsif (defined($oParent->paramGet('depend', false)))
419                {
420                    $strDependPrev = $oParent->paramGet('depend');
421                }
422
423                last;
424            }
425
426            $oLastChild = $oChild;
427        }
428
429        if (defined($strDepend))
430        {
431            if (defined($strDependPrev) && $strDepend eq $strDependPrev && !$oNode->paramTest('depend-default'))
432            {
433                &log(WARN,
434                    "section '${strPath}' depend is set to '${strDepend}' which is the default, best to remove" .
435                    " because it may become obsolete if a new section is added in between");
436            }
437        }
438        else
439        {
440            $strDepend = $strDependPrev;
441        }
442
443        # If depend is defined make sure it exists
444        if (defined($strDepend))
445        {
446            # If this is a relative depend then prepend the parent section
447            if (index($strDepend, '/') != 0)
448            {
449                if (defined($oParent->paramGet('path', false)))
450                {
451                    $strDepend = $oParent->paramGet('path') . '/' . $strDepend;
452                }
453                else
454                {
455                    $strDepend = "/${strDepend}";
456                }
457            }
458
459            if (!defined($self->{oSection}->{$strDepend}))
460            {
461                confess &log(ERROR, "section '${strSectionId}' depend '${strDepend}' is not valid");
462            }
463        }
464
465        if (defined($strDepend))
466        {
467            $oNode->paramSet('depend', $strDepend);
468        }
469
470        if (defined($strDependPrev))
471        {
472            $oNode->paramSet('depend-default', $strDependPrev);
473        }
474
475        # Set log to true if this section has an execute list.  This helps reduce the info logging by only showing sections that are
476        # likely to take a log time.
477        $oNode->paramSet('log', $self->{bExe} && $oNode->nodeList('execute-list', false) > 0 ? true : false);
478
479        # If section content is being pulled from elsewhere go get the content
480        if ($oNode->paramTest('source'))
481        {
482            my $oSource = ${$self->{oManifest}->sourceGet($oNode->paramGet('source'))}{doc};
483
484            # Section should not already have title defined, it should come from the source doc
485            if ($oNode->nodeTest('title'))
486            {
487                confess &log(ERROR, "cannot specify title in section that sources another document");
488            }
489
490            # Set title from source doc's title
491            $oNode->nodeAdd('title')->textSet($oSource->paramGet('title'));
492
493            foreach my $oSection ($oSource->nodeList('section'))
494            {
495                push(@{${$oNode->{oDoc}}{children}}, $oSection->{oDoc});
496            }
497
498            # Set path prefix to modify all section paths further down
499            $strPathPrefix = $strPath;
500
501            # Remove source so it is not included again later
502            $oNode->paramSet('source', undef);
503        }
504    }
505    # Build link
506    elsif ($strName eq 'link')
507    {
508        &log(DEBUG, 'build link [' . $oNode->valueGet() . ']');
509
510        # If the path prefix is set and this is a section
511        if (defined($strPathPrefix) && $oNode->paramTest('section'))
512        {
513            my $strNewPath = $strPathPrefix . $oNode->paramGet('section');
514            &log(DEBUG, "modify link section from '" . $oNode->paramGet('section') . "' to '${strNewPath}'");
515
516            $oNode->paramSet('section', $strNewPath);
517        }
518    }
519    # Store block defines
520    elsif ($strName eq 'block-define')
521    {
522        my $strBlockId = $oNode->paramGet('id');
523
524        if (defined($self->{oyBlockDefine}{$strBlockId}))
525        {
526            confess &log(ERROR, "block ${strBlockId} is already defined");
527        }
528
529        $self->{oyBlockDefine}{$strBlockId} = dclone($oNode->{oDoc}{children});
530        $oParent->nodeRemove($oNode);
531    }
532    # Copy blocks
533    elsif ($strName eq 'block')
534    {
535        my $strBlockId = $oNode->paramGet('id');
536
537        if (!defined($self->{oyBlockDefine}{$strBlockId}))
538        {
539            confess &log(ERROR, "block ${strBlockId} is not defined");
540        }
541
542        my $strNodeJSON = $self->{oJSON}->encode($self->{oyBlockDefine}{$strBlockId});
543
544        foreach my $oVariable ($oNode->nodeList('block-variable-replace', false))
545        {
546            my $strVariableKey = $oVariable->paramGet('key');
547            my $strVariableReplace = $oVariable->valueGet();
548
549            $strNodeJSON =~ s/\{\[$strVariableKey\]\}/$strVariableReplace/g;
550        }
551
552        my ($iReplaceIdx, $iReplaceTotal) = $oParent->nodeReplace($oNode, $self->{oJSON}->decode($strNodeJSON));
553
554        # Build any new children that were added
555        my $iChildIdx = 0;
556
557        foreach my $oChild ($oParent->nodeList(undef, false))
558        {
559            if ($iChildIdx >= $iReplaceIdx && $iChildIdx < ($iReplaceIdx + $iReplaceTotal))
560            {
561                $self->build($oChild, $oParent, $strPath, $strPathPrefix);
562            }
563
564            $iChildIdx++;
565        }
566    }
567    # Check for pre-execute statements
568    elsif ($strName eq 'execute')
569    {
570        if ($self->{oManifest}->{bPre} && $oNode->paramGet('pre', false, 'n') eq 'y')
571        {
572            # Add to pre-execute list
573            my $strHost = $self->variableReplace($oParent->paramGet('host'));
574            push(@{$self->{preExecute}{$strHost}}, $oNode);
575
576            # Skip this command so it doesn't get executed twice
577            $oNode->paramSet('skip', 'y')
578        }
579    }
580
581    # Iterate all text nodes
582    if (defined($oNode->textGet(false)))
583    {
584        foreach my $oChild ($oNode->textGet()->nodeList(undef, false))
585        {
586            if (ref(\$oChild) ne "SCALAR")
587            {
588                $self->build($oChild, $oNode, $strPath, $strPathPrefix);
589            }
590        }
591    }
592
593    # Iterate all non-text nodes
594    foreach my $oChild ($oNode->nodeList(undef, false))
595    {
596        if (ref(\$oChild) ne "SCALAR")
597        {
598            $self->build($oChild, $oNode, $strPath, $strPathPrefix);
599
600            # If the child should be logged then log the parent as well so the hierarchy is complete
601            if ($oChild->nameGet() eq 'section' && $oChild->paramGet('log', false, false))
602            {
603                $oNode->paramSet('log', true);
604            }
605        }
606    }
607}
608
609####################################################################################################################################
610# required
611#
612# Build a list of required sections
613####################################################################################################################################
614sub required
615{
616    my $self = shift;
617    my $strPath = shift;
618    my $bDepend = shift;
619
620    # If node is not found that means the path is invalid
621    my $oNode = ${$self->{oSection}}{$strPath};
622
623    if (!defined($oNode))
624    {
625        confess &log(ERROR, "invalid path ${strPath}");
626    }
627
628    # Only add sections that are listed dependencies
629    if (!defined($bDepend) || $bDepend)
630    {
631        # Match section and all child sections
632        foreach my $strChildPath (sort(keys(%{$self->{oSection}})))
633        {
634            if ($strChildPath =~ /^$strPath$/ || $strChildPath =~ /^$strPath\/.*$/)
635            {
636                if (!defined(${$self->{oSectionRequired}}{$strChildPath}))
637                {
638                    my @stryChildPath = split('/', $strChildPath);
639
640                    &log(INFO, ('    ' x (scalar(@stryChildPath) - 2)) . "        require section: ${strChildPath}");
641
642                    ${$self->{oSectionRequired}}{$strChildPath} = true;
643                }
644            }
645        }
646    }
647
648    # Get the path of the current section's parent
649    my $strParentPath = $oNode->paramGet('path-parent', false);
650
651    if ($oNode->paramTest('depend'))
652    {
653        foreach my $strDepend (split(',', $oNode->paramGet('depend')))
654        {
655            if ($strDepend !~ /^\//)
656            {
657                if (!defined($strParentPath))
658                {
659                    $strDepend = "/${strDepend}";
660                }
661                else
662                {
663                    $strDepend = "${strParentPath}/${strDepend}";
664                }
665            }
666
667            $self->required($strDepend, true);
668        }
669    }
670    elsif (defined($strParentPath))
671    {
672        $self->required($strParentPath, false);
673    }
674}
675
676####################################################################################################################################
677# isRequired
678#
679# Is it required to execute the section statements?
680####################################################################################################################################
681sub isRequired
682{
683    my $self = shift;
684    my $oSection = shift;
685
686    if (!defined($self->{oSectionRequired}))
687    {
688        return true;
689    }
690
691    my $strPath = $oSection->paramGet('path');
692
693    defined(${$self->{oSectionRequired}}{$strPath}) ? true : false;
694}
695
696####################################################################################################################################
697# processTag
698####################################################################################################################################
699sub processTag
700{
701    my $self = shift;
702
703    # Assign function parameters, defaults, and log debug info
704    my
705    (
706        $strOperation,
707        $oTag
708    ) =
709        logDebugParam
710        (
711            __PACKAGE__ . '->processTag', \@_,
712            {name => 'oTag', trace => true}
713        );
714
715    my $strBuffer = "";
716
717    my $strType = $self->{strType};
718    my $strTag = $oTag->nameGet();
719
720    if (!defined($strTag))
721    {
722        require Data::Dumper;
723        confess Dumper($oTag);
724    }
725
726    if ($strTag eq 'link')
727    {
728        my $strUrl = $oTag->paramGet('url', false);
729
730        if (!defined($strUrl))
731        {
732            my $strPage = $self->variableReplace($oTag->paramGet('page', false));
733            my $strSection = $oTag->paramGet('section', false);
734
735            # If a page/section link points to the current page then remove the page portion
736            if (defined($strPage) && defined($strSection) && defined($self->{strRenderOutKey}) &&
737                $strPage eq $self->{strRenderOutKey})
738            {
739                undef($strPage);
740            }
741
742            # If this is a page URL
743            if (defined($strPage))
744            {
745                # If the page wasn't rendered then point at the website
746                if (!defined($self->{oManifest}->renderOutGet($strType, $strPage, true)))
747                {
748                    $strUrl = '{[backrest-url-base]}/' . $oTag->paramGet('page') . '.html';
749                }
750                # Else point locally
751                else
752                {
753                    if ($strType eq 'html')
754                    {
755                        $strUrl = "${strPage}.html". (defined($strSection) ? '#' . substr($strSection, 1) : '');
756                    }
757                    elsif ($strType eq 'markdown')
758                    {
759                        if (defined($strSection))
760                        {
761                            confess &log(
762                                ERROR,
763                                "page and section links not supported for type ${strType}, value '" . $oTag->valueGet() . "'");
764                        }
765
766                        $strUrl = "${strPage}.md";
767                    }
768                    else
769                    {
770                        confess &log(ERROR, "page links not supported for type ${strType}, value '" . $oTag->valueGet() . "'");
771                    }
772                }
773            }
774            else
775            {
776                my $strSection = $oTag->paramGet('section');
777                my $oSection = ${$self->{oSection}}{$strSection};
778
779                if (!defined($oSection))
780                {
781                    confess &log(ERROR, "section link '${strSection}' does not exist");
782                }
783
784                if (!defined($strSection))
785                {
786                    confess &log(ERROR, "link with value '" . $oTag->valueGet() . "' must defined url, page, or section");
787                }
788
789                if ($strType eq 'html')
790                {
791                    $strUrl = '#' . substr($strSection, 1);
792                }
793                elsif ($strType eq 'latex')
794                {
795                    $strUrl = $strSection;
796                }
797                else
798                {
799                    $strUrl = lc($self->processText($oSection->nodeGet('title')->textGet()));
800                    $strUrl =~ s/[^\w\- ]//g;
801                    $strUrl =~ s/ /-/g;
802                    $strUrl = '#' . $strUrl;
803                }
804            }
805        }
806
807        if ($strType eq 'html')
808        {
809            $strBuffer = '<a href="' . $strUrl . '">' . $oTag->valueGet() . '</a>';
810        }
811        elsif ($strType eq 'markdown')
812        {
813            $strBuffer = '[' . $oTag->valueGet() . '](' . $strUrl . ')';
814        }
815        elsif ($strType eq 'latex')
816        {
817            if ($oTag->paramTest('url'))
818            {
819                $strBuffer = "\\href{$strUrl}{" . $oTag->valueGet() . "}";
820            }
821            else
822            {
823                $strBuffer = "\\hyperref[$strUrl]{" . $oTag->valueGet() . "}";
824            }
825        }
826        elsif ($strType eq 'text')
827        {
828            $strBuffer = $oTag->valueGet();
829        }
830        else
831        {
832            confess "'link' tag not valid for type ${strType}";
833        }
834    }
835    else
836    {
837        my $strStart = $$oRenderTag{$strType}{$strTag}[0];
838        my $strStop = $$oRenderTag{$strType}{$strTag}[1];
839
840        if (!defined($strStart) || !defined($strStop))
841        {
842            confess &log(ERROR, "invalid type ${strType} or tag ${strTag}");
843        }
844
845        $strBuffer .= $strStart;
846
847        # Admonitions in the reference materials are tags of the text element rather than field elements of the document so special
848        # handling is required
849        if ($strTag eq 'admonition')
850        {
851            $strBuffer .= $self->processAdmonitionStart($oTag);
852        }
853
854        if ($strTag eq 'p' || $strTag eq 'title' || $strTag eq 'li' || $strTag eq 'code-block' || $strTag eq 'summary'
855            || $strTag eq 'admonition')
856        {
857            $strBuffer .= $self->processText($oTag);
858        }
859        elsif (defined($oTag->valueGet()))
860        {
861            $strBuffer .= $oTag->valueGet();
862        }
863        else
864        {
865            foreach my $oSubTag ($oTag->nodeList(undef, false))
866            {
867                $strBuffer .= $self->processTag($oSubTag);
868            }
869        }
870
871        if ($strTag eq 'admonition')
872        {
873            $strBuffer .= $self->processAdmonitionEnd($oTag);
874        }
875
876        $strBuffer .= $strStop;
877    }
878
879    # Return from function and log return values if any
880    return logDebugReturn
881    (
882        $strOperation,
883        {name => 'strBuffer', value => $strBuffer, trace => true}
884    );
885}
886
887####################################################################################################################################
888# processAdmonitionStart
889####################################################################################################################################
890sub processAdmonitionStart
891{
892    my $self = shift;
893
894    # Assign function parameters, defaults, and log debug info
895    my
896    (
897        $strOperation,
898        $oTag
899    ) =
900        logDebugParam
901        (
902            __PACKAGE__ . '->processAdmonitionStart', \@_,
903            {name => 'oTag', trace => true}
904        );
905
906    my $strType = $self->{strType};
907    my $strBuffer = '';
908
909    # Note that any changes to the way the HTML, markdown or latex display tags may also need to be made here
910    if ($strType eq 'html')
911    {
912        my $strType = $oTag->paramGet('type');
913        $strBuffer = '<div class="' . $strType . '">' . uc($strType) . ':</div>' .
914            '<div class="' . $strType . '-text">';
915    }
916    elsif ($strType eq 'text' || $strType eq 'markdown')
917    {
918        $strBuffer = uc($oTag->paramGet('type')) . ": ";
919    }
920    elsif ($strType eq 'latex')
921    {
922        $strBuffer = uc($oTag->paramGet('type')) . ": }";
923    }
924
925    # Return from function and log return values if any
926    return logDebugReturn
927    (
928        $strOperation,
929        {name => 'strBuffer', value => $strBuffer, trace => true}
930    );
931}
932
933####################################################################################################################################
934# processAdmonitionEnd
935####################################################################################################################################
936sub processAdmonitionEnd
937{
938    my $self = shift;
939
940    # Assign function parameters, defaults, and log debug info
941    my
942    (
943        $strOperation,
944        $oTag
945    ) =
946        logDebugParam
947        (
948            __PACKAGE__ . '->processAdmonitionEnd', \@_,
949            {name => 'oTag', trace => true}
950        );
951
952    my $strType = $self->{strType};
953    my $strBuffer = '';
954
955    # Note that any changes to the way the HTML, markdown or latex display tags may also need to be made here
956    if ($strType eq 'html')
957    {
958        $strBuffer = '</div>';
959    }
960
961    # Return from function and log return values if any
962    return logDebugReturn
963    (
964        $strOperation,
965        {name => 'strBuffer', value => $strBuffer, trace => true}
966    );
967}
968
969####################################################################################################################################
970# processText
971####################################################################################################################################
972sub processText
973{
974    my $self = shift;
975
976    # Assign function parameters, defaults, and log debug info
977    my
978    (
979        $strOperation,
980        $oText
981    ) =
982        logDebugParam
983        (
984            __PACKAGE__ . '->processText', \@_,
985            {name => 'oText', trace => true}
986        );
987
988    my $strType = $self->{strType};
989    my $strBuffer = '';
990
991    foreach my $oNode ($oText->nodeList(undef, false))
992    {
993        if (ref(\$oNode) eq "SCALAR")
994        {
995            if ($oNode =~ /\"/)
996            {
997                confess &log(ERROR, "unable to process quotes in string (use <quote> instead):\n${oNode}");
998            }
999
1000            $strBuffer .= $oNode;
1001        }
1002        else
1003        {
1004            $strBuffer .= $self->processTag($oNode);
1005        }
1006    }
1007    #
1008    # if ($strType eq 'html')
1009    # {
1010    #         # $strBuffer =~ s/^\s+|\s+$//g;
1011    #
1012    #     $strBuffer =~ s/\n/\<br\/\>\n/g;
1013    # }
1014
1015    # if ($strType eq 'markdown')
1016    # {
1017            # $strBuffer =~ s/^\s+|\s+$//g;
1018
1019        $strBuffer =~ s/ +/ /g;
1020        $strBuffer =~ s/^ //smg;
1021    # }
1022
1023    if ($strType eq 'latex')
1024    {
1025        $strBuffer =~ s/\&mdash\;/---/g;
1026        $strBuffer =~ s/\&lt\;/\</g;
1027        $strBuffer =~ s/\<\=/\$\\leq\$/g;
1028        $strBuffer =~ s/\>\=/\$\\geq\$/g;
1029        # $strBuffer =~ s/\_/\\_/g;
1030
1031        # If not a code-block, which is to be taken AS IS, then escape special characters in latex
1032        if ($oText->nameGet() ne 'code-block')
1033        {
1034            # If the previous character is not already a slash (e.g. not already escaped) then insert a slash
1035            $strBuffer =~ s/(?<!\\)\#/\\#/g;
1036            $strBuffer =~ s/(?<!\\)\%/\\%/g;
1037            $strBuffer =~ s/(?<!\\)\_/\\_/g;
1038            # $strBuffer =~ s/(?<!\\)\$/\\\$/g;
1039
1040            # Escape square brackest in list items since they are used for reformatting the bullet item with what is in brackets,
1041            # which is not the intention.
1042            if ($oText->nameGet() eq 'list-item')
1043            {
1044                $strBuffer =~ s/\[/\{\[/g;
1045                $strBuffer =~ s/\]/\]\}/g;
1046            }
1047
1048            $strBuffer =~ s/\&copy\;/{\\textcopyright}/g;
1049            $strBuffer =~ s/\&trade\;/{\\texttrademark}/g;
1050            $strBuffer =~ s/\&reg\;/{\\textregistered}/g;
1051
1052            $strBuffer =~ s/\&rarr\;/{\\textrightarrow}/g;
1053
1054            # Escape all ampersands after making any other conversions above
1055            $strBuffer =~ s/(?<!\\)\&/\\&/g;
1056        }
1057    }
1058
1059    if ($strType eq 'text')
1060    {
1061        $strBuffer =~ s/\&mdash\;/--/g;
1062        $strBuffer =~ s/\&lt\;/\</g;
1063        $strBuffer =~ s/\&ge\;/\>\=/g;
1064    }
1065
1066    $strBuffer = $self->variableReplace($strBuffer);
1067
1068    # Return from function and log return values if any
1069    return logDebugReturn
1070    (
1071        $strOperation,
1072        {name => 'strBuffer', value => $strBuffer, trace => true}
1073    );
1074}
1075
10761;
1077