File Coverage

blib/lib/LaTeXML/Core.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1             # /=====================================================================\ #
2             # | LaTeXML | #
3             # | Core Module for TeX conversion | #
4             # |=====================================================================| #
5             # | Part of LaTeXML: | #
6             # | Public domain software, produced as part of work done by the | #
7             # | United States Government & not subject to copyright in the US. | #
8             # |---------------------------------------------------------------------| #
9             # | Bruce Miller #_# | #
10             # | http://dlmf.nist.gov/LaTeXML/ (o o) | #
11             # \=========================================================ooo==U==ooo=/ #
12              
13             package LaTeXML::Core;
14 21     21   70 use strict;
  21         24  
  21         475  
15 21     21   61 use warnings;
  21         20  
  21         482  
16 21     21   6133 use LaTeXML::Global;
  21         30  
  21         1730  
17             #use LaTeXML::Common::Object;
18 21     21   7812 use LaTeXML::Common::Error;
  0            
  0            
19             use LaTeXML::Core::State;
20             use LaTeXML::Core::Token;
21             use LaTeXML::Core::Tokens;
22             use LaTeXML::Core::Stomach;
23             use LaTeXML::Core::Document;
24             use LaTeXML::Common::Model;
25             use LaTeXML::MathParser;
26             use LaTeXML::Util::Pathname;
27             use LaTeXML::Pre::BibTeX;
28             use LaTeXML::Package; # !!!!
29             use LaTeXML::Version;
30             use Encode;
31             use FindBin;
32             use base qw(LaTeXML::Common::Object);
33              
34             #**********************************************************************
35              
36             sub new {
37             my ($class, %options) = @_;
38             my $state = LaTeXML::Core::State->new(catcodes => 'standard',
39             stomach => LaTeXML::Core::Stomach->new(),
40             model => $options{model} || LaTeXML::Common::Model->new());
41             $state->assignValue(VERBOSITY => (defined $options{verbosity} ? $options{verbosity} : 0),
42             'global');
43             $state->assignValue(STRICT => (defined $options{strict} ? $options{strict} : 0),
44             'global');
45             $state->assignValue(INCLUDE_COMMENTS => (defined $options{includeComments} ? $options{includeComments} : 1),
46             'global');
47             $state->assignValue(DOCUMENTID => (defined $options{documentid} ? $options{documentid} : ''),
48             'global');
49             $state->assignValue(SEARCHPATHS => [map { pathname_absolute(pathname_canonical($_)) }
50             @{ $options{searchpaths} || [] }],
51             'global');
52             $state->assignValue(GRAPHICSPATHS => [map { pathname_absolute(pathname_canonical($_)) }
53             @{ $options{graphicspaths} || [] }], 'global');
54             $state->assignValue(INCLUDE_STYLES => $options{includeStyles} || 0, 'global');
55             $state->assignValue(PERL_INPUT_ENCODING => $options{inputencoding}) if $options{inputencoding};
56             $state->assignValue(NOMATHPARSE => $options{nomathparse} || 0, 'global');
57             return bless { state => $state,
58             nomathparse => $options{nomathparse} || 0,
59             preload => $options{preload},
60             }, $class; }
61              
62             #%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
63             # High-level API.
64              
65             sub convertAndWriteFile {
66             my ($self, $file) = @_;
67             $file =~ s/\.tex$//;
68             my $dom = $self->convertFile($file);
69             $dom->toFile("$file.xml", 1) if $dom;
70             return $dom; }
71              
72             sub convertFile {
73             my ($self, $file) = @_;
74             my $digested = $self->digestFile($file);
75             return unless $digested;
76             return $self->convertDocument($digested); }
77              
78             sub getStatusMessage {
79             my ($self) = @_;
80             return $$self{state}->getStatusMessage; }
81              
82             sub getStatusCode {
83             my ($self) = @_;
84             return $$self{state}->getStatusCode; }
85              
86             # You'd typically do this after both digestion AND conversion...
87             sub showProfile {
88             my ($self, $digested) = @_;
89             return
90             $self->withState(sub {
91             LaTeXML::Core::Definition::showProfile(); # Show profile (if any)
92             }); }
93              
94             #%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
95             # Mid-level API.
96              
97             # options are currently being evolved to accomodate the Daemon:
98             # mode : the processing mode, ie the pool to preload: TeX or BibTeX
99             # noinitialize : if defined, it does not initialize State.
100             # preamble = names a tex file (or standard_preamble.tex)
101             # postamble = names a tex file (or standard_postamble.tex)
102              
103             our %MODE_EXTENSION = ( # CONFIGURATION?
104             TeX => 'tex', LaTeX => 'tex', AmSTeX => 'tex', BibTeX => 'bib');
105              
106             sub digestFile {
107             my ($self, $request, %options) = @_;
108             my ($dir, $name, $ext);
109             my $mode = $options{mode} || 'TeX';
110             if (pathname_is_literaldata($request)) {
111             $dir = undef; $ext = $MODE_EXTENSION{$mode};
112             $name = "Anonymous String"; }
113             elsif (pathname_is_url($request)) {
114             $dir = undef; $ext = $MODE_EXTENSION{$mode};
115             $name = $request;
116             }
117             else {
118             $request =~ s/\.\Q$MODE_EXTENSION{$mode}\E$//;
119             if (my $pathname = pathname_find($request, types => [$MODE_EXTENSION{$mode}, ''])) {
120             $request = $pathname;
121             ($dir, $name, $ext) = pathname_split($request); }
122             else {
123             $self->withState(sub {
124             Fatal('missing_file', $request, undef, "Can't find $mode file $request"); }); } }
125             return
126             $self->withState(sub {
127             my ($state) = @_;
128             NoteBegin("Digesting $mode $name");
129             $self->initializeState($mode . ".pool", @{ $$self{preload} || [] }) unless $options{noinitialize};
130             $state->assignValue(SOURCEFILE => $request) if (!pathname_is_literaldata($request));
131             $state->assignValue(SOURCEDIRECTORY => $dir) if defined $dir;
132             $state->unshiftValue(SEARCHPATHS => $dir)
133             if defined $dir && !grep { $_ eq $dir } @{ $state->lookupValue('SEARCHPATHS') };
134             $state->unshiftValue(GRAPHICSPATHS => $dir)
135              
136             if defined $dir && !grep { $_ eq $dir } @{ $state->lookupValue('GRAPHICSPATHS') };
137              
138             $state->installDefinition(LaTeXML::Core::Definition::Expandable->new(T_CS('\jobname'), undef,
139             Tokens(Explode($name))));
140             # Reverse order, since last opened is first read!
141             $self->loadPostamble($options{postamble}) if $options{postamble};
142             LaTeXML::Package::InputContent($request);
143             $self->loadPreamble($options{preamble}) if $options{preamble};
144              
145             # Now for the Hacky part for BibTeX!!!
146             if ($mode eq 'BibTeX') {
147             my $bib = LaTeXML::Pre::BibTeX->newFromGullet($name, $state->getStomach->getGullet);
148             LaTeXML::Package::InputContent("literal:" . $bib->toTeX); }
149             my $list = $self->finishDigestion;
150             NoteEnd("Digesting $mode $name");
151             return $list; });
152             }
153              
154             sub finishDigestion {
155             my ($self) = @_;
156             my $state = $$self{state};
157             my $stomach = $state->getStomach;
158             my @stuff = ();
159             while ($stomach->getGullet->getMouth->hasMoreInput) {
160             push(@stuff, $stomach->digestNextBody); }
161             if (my $env = $state->lookupValue('current_environment')) {
162             Error('expected', "\\end{$env}", $stomach,
163             "Input ended while environment $env was open"); }
164             my $ifstack = $state->lookupValue('if_stack');
165             if ($ifstack && $$ifstack[0]) {
166             Error('expected', '\fi', $stomach,
167             "Input ended while conditional " . ToString($$ifstack[0]{token}) . " was incomplete",
168             "started at " . ToString($$ifstack[0]{start})); }
169             $stomach->getGullet->flush;
170             return List(@stuff); }
171              
172             sub loadPreamble {
173             my ($self, $preamble) = @_;
174             my $gullet = $$self{state}->getStomach->getGullet;
175             if ($preamble eq 'standard_preamble.tex') {
176             $preamble = 'literal:\documentclass{article}\begin{document}'; }
177             return LaTeXML::Package::InputContent($preamble); }
178              
179             sub loadPostamble {
180             my ($self, $postamble) = @_;
181             my $gullet = $$self{state}->getStomach->getGullet;
182             if ($postamble eq 'standard_postamble.tex') {
183             $postamble = 'literal:\end{document}'; }
184             return LaTeXML::Package::InputContent($postamble); }
185              
186             sub convertDocument {
187             my ($self, $digested) = @_;
188             return
189             $self->withState(sub {
190             my ($state) = @_;
191             my $model = $state->getModel; # The document model.
192             my $document = LaTeXML::Core::Document->new($model);
193             local $LaTeXML::DOCUMENT = $document;
194             NoteBegin("Building");
195             $model->loadSchema(); # If needed?
196             if (my $paths = $state->lookupValue('SEARCHPATHS')) {
197             if ($state->lookupValue('INCLUDE_COMMENTS')) {
198             $document->insertPI('latexml', searchpaths => join(',', @$paths)); } }
199             foreach my $preload (@{ $$self{preload} }) {
200             next if $preload =~ /\.pool$/;
201             my $options = undef; # Stupid perlcritic policy
202             if ($preload =~ s/^\[([^\]]*)\]//) { $options = $1; }
203             if ($preload =~ s/\.cls$//) {
204             $document->insertPI('latexml', class => $preload, ($options ? (options => $options) : ())); }
205             else {
206             $preload =~ s/\.sty$//;
207             $document->insertPI('latexml', package => $preload, ($options ? (options => $options) : ())); } }
208             { no warnings 'recursion';
209             $document->absorb($digested); }
210             NoteEnd("Building");
211              
212             if (my $rules = $state->lookupValue('DOCUMENT_REWRITE_RULES')) {
213             NoteBegin("Rewriting");
214             $document->markXMNodeVisibility;
215             foreach my $rule (@$rules) {
216             $rule->rewrite($document, $document->getDocument->documentElement); }
217             NoteEnd("Rewriting"); }
218              
219             LaTeXML::MathParser->new()->parseMath($document) unless $$self{nomathparse};
220             NoteBegin("Finalizing");
221             my $xmldoc = $document->finalize();
222             NoteEnd("Finalizing");
223             return $xmldoc; }); }
224              
225             sub withState {
226             my ($self, $closure) = @_;
227             local $STATE = $$self{state};
228             # And, set fancy error handler for ANY die!
229             local $SIG{__DIE__} = \&LaTeXML::Common::Error::perl_die_handler;
230             local $SIG{INT} = \&LaTeXML::Common::Error::perl_interrupt_handler;
231             local $SIG{__WARN__} = \&LaTeXML::Common::Error::perl_warn_handler;
232             local $SIG{'ALRM'} = \&LaTeXML::Common::Error::perl_timeout_handler;
233             local $SIG{'TERM'} = \&LaTeXML::Common::Error::perl_terminate_handler;
234              
235             local $LaTeXML::DUAL_BRANCH = '';
236              
237             return &$closure($STATE); }
238              
239             sub initializeState {
240             my ($self, @files) = @_;
241             my $state = $$self{state};
242             my $stomach = $state->getStomach; # The current Stomach;
243             my $gullet = $stomach->getGullet;
244             $stomach->initialize;
245             my $paths = $state->lookupValue('SEARCHPATHS');
246             $state->assignValue('InitialPreloads' => 1, 'global');
247             foreach my $preload (@files) {
248             my ($options, $type);
249             $options = $1 if $preload =~ s/^\[([^\]]*)\]//;
250             $type = ($preload =~ s/\.(\w+)$// ? $1 : 'sty');
251             my $handleoptions = ($type eq 'sty') || ($type eq 'cls');
252             if ($options) {
253             if ($handleoptions) {
254             $options = [split(/,/, $options)]; }
255             else {
256             Warn('unexpected', 'options',
257             "Attempting to pass options to $preload.$type (not style or class)",
258             "The options were [$options]"); } }
259             # Attach extension back if HTTP protocol:
260             if (pathname_is_url($preload)) {
261             $preload .= '.' . $type;
262             }
263             LaTeXML::Package::InputDefinitions($preload, type => $type,
264             handleoptions => $handleoptions, options => $options);
265             }
266             $state->assignValue('InitialPreloads' => undef, 'global');
267             return; }
268              
269             sub writeDOM {
270             my ($self, $dom, $name) = @_;
271             $dom->toFile("$name.xml", 1);
272             return 1; }
273              
274             #**********************************************************************
275             # Should post processing be managed from here too?
276             # Problem: with current DOM setup, I pretty much have to write the
277             # file and reread it anyway...
278             # Also, want to inhibit loading an extreme number of classes if not needed.
279             #**********************************************************************
280             1;
281              
282             __END__