1# /=====================================================================\ # 2# | LaTeXML::Common::Model | # 3# | Stores representation of Document Type for use by Document | # 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 <bruce.miller@nist.gov> #_# | # 10# | http://dlmf.nist.gov/LaTeXML/ (o o) | # 11# \=========================================================ooo==U==ooo=/ # 12package LaTeXML::Common::Model; 13use strict; 14use warnings; 15use LaTeXML::Global; 16use LaTeXML::Common::Object; 17use LaTeXML::Common::Error; 18use LaTeXML::Common::Font; 19use LaTeXML::Common::XML; 20use LaTeXML::Util::Pathname; 21use base qw(LaTeXML::Common::Object); 22 23#********************************************************************** 24my $LTX_NAMESPACE = "http://dlmf.nist.gov/LaTeXML"; # [CONSTANT] 25 26sub new { 27 my ($class, %options) = @_; 28 my $self = bless { xpath => LaTeXML::Common::XML::XPath->new(), 29 code_namespace_prefixes => {}, code_namespaces => {}, 30 doctype_namespaces => {}, 31 namespace_errors => 0, 32 %options }, $class; 33 $$self{xpath}->registerFunction('match-font', \&LaTeXML::Common::Font::match_font); 34 $self->registerNamespace('xml', $LaTeXML::Common::XML::XML_NS); 35 $self->registerDocumentNamespace('xml', $LaTeXML::Common::XML::XML_NS); 36 return $self; } 37 38sub setDocType { 39 my ($self, $roottag, $publicid, $systemid) = @_; 40 $$self{schemadata} = ['DTD', $roottag, $publicid, $systemid]; 41 return; } 42 43sub setRelaxNGSchema { 44 my ($self, $schema) = @_; 45 $$self{schemadata} = ['RelaxNG', $schema]; 46 return; } 47 48sub loadSchema { 49 my ($self) = @_; 50 return $$self{schema} if $$self{schema_loaded}; 51 my $name; 52 53 if (!$$self{schemadata}) { 54 Warn('expected', '<model>', undef, "No Schema Model has been declared; assuming LaTeXML"); 55 # article ??? or what ? undef gives problems! 56 $self->setRelaxNGSchema("LaTeXML"); 57 $self->registerNamespace(ltx => $LTX_NAMESPACE); 58 $self->registerNamespace(svg => "http://www.w3.org/2000/svg"); 59 $self->registerNamespace(xlink => "http://www.w3.org/1999/xlink"); # Needed for SVG 60 $self->registerNamespace(m => "http://www.w3.org/1998/Math/MathML"); 61 $self->registerNamespace(xhtml => "http://www.w3.org/1999/xhtml"); 62 $$self{permissive} = 1; } # Actually, they could have declared all sorts of Tags.... 63 64 my ($type, @data) = @{ $$self{schemadata} }; 65 if ($type eq 'DTD') { 66 my ($roottag, $publicid, $systemid) = @data; 67 require LaTeXML::Common::Model::DTD; 68 $name = $systemid; 69 $$self{schema} = LaTeXML::Common::Model::DTD->new($self, $roottag, $publicid, $systemid); } 70 elsif ($type eq 'RelaxNG') { 71 ($name) = @data; 72 require LaTeXML::Common::Model::RelaxNG; 73 $$self{schema} = LaTeXML::Common::Model::RelaxNG->new($self, $name); } 74 75 if (my $compiled = !$$self{no_compiled} 76 && pathname_find($name, paths => $STATE->lookupValue('SEARCHPATHS'), 77 types => ['model'], installation_subdir => "resources/$type")) { 78 $self->loadCompiledSchema($compiled); } 79 else { 80 $$self{schema}->loadSchema; } 81 $self->describeModel if $LaTeXML::Common::Model::DEBUG; 82 $$self{schema_loaded} = 1; 83 return $$self{schema}; } 84 85sub addSchemaDeclaration { 86 my ($self, $document, $tag) = @_; 87 $$self{schema}->addSchemaDeclaration($document, $tag); 88 return; } 89 90#===================================================================== 91# Make provision to precompile the schema. 92sub compileSchema { 93 my ($self) = @_; 94 $$self{no_compiled} = 1; 95 $self->loadSchema; 96 foreach my $prefix (sort keys %{ $$self{document_namespaces} }) { 97 print $prefix. '=' . $$self{document_namespaces}{$prefix} . "\n"; } 98 if (my $defs = $$self{schemaclass}) { 99 foreach my $classname (sort keys %$defs) { 100 print $classname. ':=(' . join(',', sort keys %{ $$self{schemaclass}{$classname} }) . ')' . "\n"; } } 101 foreach my $tag (sort keys %{ $$self{tagprop} }) { 102 next if $tag =~ /^!/; # Ignore top-level negated (only make sense in content model) 103 print $tag 104 . '{' . join(',', sort keys %{ $$self{tagprop}{$tag}{attributes} }) . '}' 105 . '(' . join(',', sort keys %{ $$self{tagprop}{$tag}{model} }) . ')' . "\n"; } 106 return; } 107 108sub loadCompiledSchema { 109 my ($self, $file) = @_; 110 ProgressSpinup("Loading compiled schema $file"); 111 my $MODEL; 112 open($MODEL, '<', $file) or Fatal('I/O', $file, undef, "Cannot open Compiled Model $file for reading", $!); 113 my $line; 114 while ($line = <$MODEL>) { 115 if ($line =~ /^([^\{]+)\{(.*?)\}\((.*?)\)$/) { 116 my ($tag, $attr, $children) = ($1, $2, $3); 117 $self->addTagAttribute($tag, split(/,/, $attr)); 118 $self->addTagContent($tag, split(/,/, $children)); } 119 120 elsif ($line =~ /^([^:=]+):=(.*?)$/) { 121 my ($classname, $elements) = ($1, $2); 122 $self->setSchemaClass($classname, { map { ($_ => 1) } split(/,/, $elements) }); } 123 elsif ($line =~ /^([^=]+)=(.*?)$/) { 124 my ($prefix, $namespace) = ($1, $2); 125 $self->registerDocumentNamespace($prefix, $namespace); } 126 else { 127 Fatal('internal', $file, undef, "Compiled model '$file' is malformatted at \"$line\""); } 128 } 129 close($MODEL); 130 ProgressSpindown("Loading compiled schema $file"); 131 return; } 132 133#********************************************************************** 134# Namespaces 135#********************************************************************** 136# There are TWO namespace mappings!!! 137# One for coding, one for the DocType. 138# 139# Coding: this namespace mapping associates prefixes to namespace URIs for 140# use in the latexml code, constructors and such. 141# This must be a one to one mapping and there are no default namespaces. 142# Document: this namespace mapping associates prefixes to namespace URIs 143# as used in the generated document, and will be the 144# set of prefixes used in the generated output. 145# This mapping may also use a prefix of "#default" which is for 146# the unprefixed form of elements (not used for attributes!) 147sub registerNamespace { 148 my ($self, $codeprefix, $namespace) = @_; 149 if ($namespace) { 150 $$self{code_namespace_prefixes}{$namespace} = $codeprefix; 151 $$self{code_namespaces}{$codeprefix} = $namespace; 152 $$self{xpath}->registerNS($codeprefix, $namespace); } 153 else { 154 my $prev = $$self{code_namespaces}{$codeprefix}; 155 delete $$self{code_namespace_prefixes}{$prev} if $prev; 156 delete $$self{code_namespaces}{$codeprefix}; } 157 return; } 158 159# In the following: 160# $forattribute is 1 if the namespace is for an attribute (in which case, there must be a non-empty prefix) 161# $probe, if non 0, just test for namespace, without creating an entry if missing. 162# Get the (code) prefix associated with $namespace, 163# creating a dummy prefix and signalling an error if none has been registered. 164sub getNamespacePrefix { 165 my ($self, $namespace, $forattribute, $probe) = @_; 166 if ($namespace) { 167 my $codeprefix = $$self{code_namespace_prefixes}{$namespace}; 168 if ((!defined $codeprefix) && !$probe) { 169 my $docprefix = $$self{document_namespace_prefixes}{$namespace}; 170 # if there's a doc prefix and it's NOT already used in code namespace mapping 171 if ($docprefix && !$$self{code_namespaces}{$docprefix}) { 172 $codeprefix = $docprefix; } 173 else { # Else synthesize one 174 $codeprefix = "namespace" . (++$$self{namespace_errors}); } 175 $self->registerNamespace($codeprefix, $namespace); 176 Warn('malformed', $namespace, undef, 177 "No prefix has been registered for namespace '$namespace' (in code)", 178 "Using '$codeprefix' instead"); } 179 return $codeprefix; } } 180 181sub getNamespace { 182 my ($self, $codeprefix, $probe) = @_; 183 my $ns = $$self{code_namespaces}{$codeprefix}; 184 if ((!defined $ns) && !$probe) { 185 $self->registerNamespace($codeprefix, 186 $ns = "http://example.com/namespace" . (++$$self{namespace_errors})); 187 Error('malformed', $codeprefix, undef, 188 "No namespace has been registered for prefix '$codeprefix' (in code)", 189 "Using '$ns' instead"); } 190 return $ns; } 191 192sub registerDocumentNamespace { 193 my ($self, $docprefix, $namespace) = @_; 194 $docprefix = '#default' unless defined $docprefix; 195 if ($namespace) { 196 # Since the default namespace url can still ALSO have a prefix associated, 197 # we prepend "DEFAULT#url" when using as a hash key in the prefixes table. 198 my $regnamespace = ($docprefix eq '#default' ? "DEFAULT#" . $namespace : $namespace); 199 $$self{document_namespace_prefixes}{$regnamespace} = $docprefix; 200 $$self{document_namespaces}{$docprefix} = $namespace; } 201 else { 202 my $prev = $$self{document_namespaces}{$docprefix}; 203 delete $$self{document_namespace_prefixes}{$prev} if $prev; 204 delete $$self{document_namespaces}{$docprefix}; } 205 return; } 206 207sub getDocumentNamespacePrefix { 208 my ($self, $namespace, $forattribute, $probe) = @_; 209 if ($namespace) { 210 # Get the prefix associated with the namespace url, noting that for elements, it might by "#default", 211 # but for attributes would never be. 212 my $docprefix = (!$forattribute && $$self{document_namespace_prefixes}{ "DEFAULT#" . $namespace }) 213 || $$self{document_namespace_prefixes}{$namespace}; 214 if ((!defined $docprefix) && !$probe) { 215 $self->registerDocumentNamespace($docprefix = "namespace" . (++$$self{namespace_errors}), $namespace); 216 Warn('malformed', $namespace, undef, 217 "No prefix has been registered for namespace '$namespace' (in document)", 218 "Using '$docprefix' instead"); } 219 return (($docprefix || '#default') eq '#default' ? '' : $docprefix); } } 220 221sub getDocumentNamespace { 222 my ($self, $docprefix, $probe) = @_; 223 $docprefix = '#default' unless defined $docprefix; 224 my $ns = $$self{document_namespaces}{$docprefix}; 225 $ns =~ s/^DEFAULT#// if $ns; # Remove the default hack, if present! 226 if (($docprefix ne '#default') && (!defined $ns) && !$probe) { 227 $self->registerDocumentNamespace($docprefix, 228 $ns = "http://example.com/namespace" . (++$$self{namespace_errors})); 229 Error('malformed', $docprefix, undef, 230 "No namespace has been registered for prefix '$docprefix' (in document)", 231 "Using '$ns' instead"); } 232 return $ns; } 233 234# Given a Qualified name, possibly prefixed with a namespace prefix, 235# as defined by the code namespace mapping, 236# return the NamespaceURI and localname. 237sub decodeQName { 238 my ($self, $codetag) = @_; 239 if ($codetag =~ /^([^:]+):(.+)$/) { 240 my ($prefix, $localname) = ($1, $2); 241 return (undef, $codetag) if $prefix eq 'xml'; 242 return ($self->getNamespace($prefix), $localname); } 243 else { 244 return (undef, $codetag); } } 245 246sub encodeQName { 247 my ($self, $ns, $name) = @_; 248 my $codeprefix = $ns && $self->getNamespacePrefix($ns); 249 return ($codeprefix ? "$codeprefix:$name" : $name); } 250 251# Get the node's qualified name in standard form 252# Ie. using the registered (code) prefix for that namespace. 253# NOTE: Reconsider how _Capture_ & _WildCard_ should be integrated!?! 254sub getNodeQName { 255 my ($self, $node) = @_; 256 my $type = ($node ? $node->nodeType : -1); 257 if ($type == XML_TEXT_NODE) { 258 return '#PCDATA'; } 259 elsif ($type == XML_DOCUMENT_NODE) { 260 return '#Document'; } 261 elsif ($type == XML_COMMENT_NODE) { 262 return '#Comment'; } 263 elsif ($type == XML_PI_NODE) { 264 return '#ProcessingInstruction'; } 265 elsif ($type == XML_DTD_NODE) { 266 return '#DTD'; } 267 elsif ($type == XML_NAMESPACE_DECL) { 268 my $ns = $node->declaredURI; 269 my $prefix = $ns && $self->getNamespacePrefix($ns, 0, 1); 270 return ($prefix ? 'xmlns:' . $prefix : 'xmlns'); } 271 # Need others? 272 elsif (($type != XML_ELEMENT_NODE) && ($type != XML_ATTRIBUTE_NODE)) { 273 Fatal('misdefined', '<caller>', undef, 274 "Should not ask for Qualified Name for node of type $type: " . Stringify($node)); 275 return; } 276 else { 277 my $ns = $node->namespaceURI; 278 my $prefix = $ns && $self->getNamespacePrefix($ns, 0, 1); 279 return ($prefix ? $prefix . ":" . $node->localname : $node->localname); } } 280 281# Same thing, but using the Document namespace prefixes 282sub getNodeDocumentQName { 283 my ($self, $node) = @_; 284 my $type = $node->nodeType; 285 if ($type == XML_TEXT_NODE) { 286 return '#PCDATA'; } 287 elsif ($type == XML_DOCUMENT_NODE) { 288 return '#Document'; } 289 elsif ($type == XML_COMMENT_NODE) { 290 return '#Comment'; } 291 elsif ($type == XML_PI_NODE) { 292 return '#ProcessingInstruction'; } 293 elsif ($type == XML_DTD_NODE) { 294 return '#DTD'; } 295 elsif ($type == XML_NAMESPACE_DECL) { 296 my $ns = $node->declaredURI; 297 my $prefix = $ns && $self->getDocumentNamespacePrefix($ns, 0, 1); 298 return ($prefix ? 'xmlns:' . $prefix : 'xmlns'); } 299 # Need others? 300 elsif (($type != XML_ELEMENT_NODE) && ($type != XML_ATTRIBUTE_NODE)) { 301 Fatal('misdefined', '<caller>', undef, 302 "Should not ask for Qualified Name for node of type $type: " . Stringify($node)); 303 return; } 304 else { 305 my $ns = $node->namespaceURI; 306 my $prefix = $ns && $self->getDocumentNamespacePrefix($ns, 0, 1); 307 return ($prefix ? $prefix . ":" . $node->localname : $node->localname); } } 308 309# Given a Document QName, convert to "code" form 310# Used to convert a possibly prefixed name from the DTD 311# (using the DTD's prefixes) 312# into a prefixed name using the Code's prefixes 313# NOTE: Used only for DTD 314sub recodeDocumentQName { 315 my ($self, $docQName) = @_; 316 my ($docprefix, $name) = (undef, $docQName); 317 if ($docQName =~ /^(#PCDATA|#Comment|ANY|#ProcessingInstruction|#Document)$/) { 318 return $docQName; } 319 else { 320 ($docprefix, $name) = ($1, $2) if $docQName =~ /^([^:]+):(.+)/; 321 return $self->encodeQName($self->getDocumentNamespace($docprefix), $name); } } 322 323# Get an XPath context that knows about our namespace mappings. 324sub getXPath { 325 my ($self) = @_; 326 return $$self{xpath}; } 327 328#********************************************************************** 329# Accessors 330#********************************************************************** 331 332sub getTags { 333 my ($self) = @_; 334 return (sort keys %{ $$self{tagprop} }); } 335 336sub getTagContents { 337 my ($self, $tag) = @_; 338 my $h = $$self{tagprop}{$tag}{model}; 339 return ($h ? sort keys %$h : ()); } 340 341sub addTagContent { 342 my ($self, $tag, @elements) = @_; 343 $$self{tagprop}{$tag}{model} = {} unless $$self{tagprop}{$tag}{model}; 344 map { $$self{tagprop}{$tag}{model}{$_} = 1 } @elements; 345 return; } 346 347sub getTagAttributes { 348 my ($self, $tag) = @_; 349 my $h = $$self{tagprop}{$tag}{attributes}; 350 return $h ? sort keys %$h : (); } 351 352sub addTagAttribute { 353 my ($self, $tag, @attributes) = @_; 354 $$self{tagprop}{$tag}{attributes} = {} unless $$self{tagprop}{$tag}{attributes}; 355 map { $$self{tagprop}{$tag}{attributes}{$_} = 1 } @attributes; 356 return; } 357 358sub setSchemaClass { 359 my ($self, $classname, $content) = @_; 360 $$self{schemaclass}{$classname} = $content; 361 return; } 362 363#********************************************************************** 364# Document Structure Queries 365#********************************************************************** 366# NOTE: These are public, but perhaps should be passed 367# to submodel, in case it can evolve to more precision? 368# However, it would need more context to do that. 369 370# Can an element with (qualified name) $tag contain a $childtag element? 371sub canContain { 372 my ($self, $tag, $childtag) = @_; 373 $self->loadSchema unless $$self{schema_loaded}; 374 # Handle obvious cases explicitly. 375 return 0 if !$tag || ($tag eq '#PCDATA') || ($tag eq '#Comment'); 376 return 1 if $tag =~ /(.*?:)?_Capture_$/; # with or without namespace prefix 377 return 1 if $tag eq '_WildCard_'; 378 return 1 if $childtag =~ /(.*?:)?_Capture_$/; 379 return 1 if $childtag eq '_WildCard_'; 380 return 1 if $childtag eq '#Comment'; 381 return 1 if $childtag eq '#ProcessingInstruction'; 382 return 1 if $childtag eq '#DTD'; 383 # return 1 if $$self{permissive}; # No DTD? Punt! 384 return 1 if $$self{permissive} && ($tag eq '#Document') && ($childtag ne '#PCDATA'); # No DTD? Punt! 385 # Else query tag properties. 386 my $model = $$self{tagprop}{$tag}{model}; 387 if (!$model && ($tag =~ /^(\w*):/)) { 388 my $xtag = $1 . ':*'; 389 $model = $$self{tagprop}{$xtag}{model}; } 390 my ($chns, $chname) = ($childtag =~ /^([^:]*):(.*)$/ ? ($1, $2) : (undef, $childtag)); 391 if ($chns) { 392 return ($$model{$childtag} ? 1 393 : ($$model{"!$childtag"} ? 0 394 : ($$model{"$chns:*"} ? 1 395 : ($$model{"!$chns:*"} ? 0 396 : ($$model{ANY} ? 1 397 : 0))))); } 398 else { 399 return ($$model{$childtag} ? 1 400 : ($$model{"!$childtag"} ? 0 401 : ($$model{ANY} ? 1 402 : 0))); } } 403 404# NOTE: Currently the Document class already allows ANY namespaced attributes! 405# (which is very unmodular, although it does have to arrange for namespace declarations) 406sub canHaveAttribute { 407 my ($self, $tag, $attrib) = @_; 408 $self->loadSchema unless $$self{schema_loaded}; 409 return 0 if $tag eq '#PCDATA'; 410 return 0 if $tag eq '#Comment'; 411 return 0 if $tag eq '#Document'; 412 return 0 if $tag eq '#ProcessingInstruction'; 413 return 0 if $tag eq '#DTD'; 414 return 1 if $tag =~ /(.*?:)?_Capture_$/; 415 return 1 if $$self{permissive}; 416 my $attr = $$self{tagprop}{$tag}{attributes}; 417 418 if (!$attr && ($tag =~ /^(\w*):/)) { 419 my $xtag = $1 . ':*'; 420 $attr = $$self{tagprop}{$xtag}{attributes}; } 421 return $$attr{ANY} || $$attr{$attrib}; } 422 423sub isInSchemaClass { 424 my ($self, $classname, $tag) = @_; 425 $tag = $self->getNodeQName($tag) if ref $tag; # In case tag is a node. 426 my $class = $$self{schemaclass}{$classname}; 427 return $class && $$class{$tag}; } 428 429#********************************************************************** 430sub describeModel { 431 my ($self) = @_; 432 Debug("Doctype"); 433 foreach my $tag (sort keys %{ $$self{tagprop} }) { 434 if (my $model = $$self{tagprop}{$tag}{model}) { 435 if (keys %$model) { 436 Debug("$tag can contain " . join(', ', sort keys %{ $$self{tagprop}{$tag}{model} })); } } 437 else { 438 Debug("$tag is empty"); } 439 } 440 return; } 441 442#********************************************************************** 4431; 444 445__END__ 446 447=pod 448 449=head1 NAME 450 451C<LaTeXML::Common::Model> - represents the Document Model 452 453=head1 DESCRIPTION 454 455C<LaTeXML::Common::Model> encapsulates information about the document model to be used 456in converting a digested document into XML by the L<LaTeXML::Core::Document>. 457This information is based on the document schema (eg, DTD, RelaxNG), 458but is also modified by package modules; thus the model may not be 459complete until digestion is completed. 460 461The kinds of information that is relevant is not only the content model 462(what each element can contain contain), but also SGML-like information 463such as whether an element can be implicitly opened or closed, if needed 464to insert a new element into the document. 465 466Currently, only an approximation to the schema is understood and used. 467For example, we only record that certain elements can appear within another; 468we don't preserve any information about required order or number of instances. 469 470It extends L<LaTeXML::Common::Object>. 471 472=head2 Model Creation 473 474=over 4 475 476=item C<< $model = LaTeXML::Common::Model->new(%options); >> 477 478Creates a new model. The only useful option is 479C<< permissive=>1 >> which ignores any DTD and allows the 480document to be built without following any particular content model. 481 482=back 483 484=head2 Document Type 485 486=over 4 487 488=item C<< $model->setDocType($rootname,$publicid,$systemid,%namespaces); >> 489 490Declares the expected rootelement, the public and system ID's of the document type 491to be used in the final document. The hash C<%namespaces> specifies 492the namespace prefixes that are expected to be found in the DTD, along with 493the associated namespace URI. These prefixes may be different from 494the prefixes used in implementation code (eg. in ltxml files; see RegisterNamespace). 495The generated document will use the namespaces and prefixes defined here. 496 497=back 498 499=head2 Namespaces 500 501Note that there are I<two> namespace mappings between namespace URIs and prefixes 502that are relevant to L<LaTeXML>. 503The `code' mapping is the one used in code implementing packages, and in 504particular, constructors defined within those packages. The prefix C<ltx> 505is used consistently to refer to L<LaTeXML>'s own namespace 506(C<http://dlmf.nist.gov/LaTeXML)>. 507 508The other mapping, the `document' mapping, is used in the created document; 509this may be different from the `code' mapping in order to accommodate 510DTDs, for example, or for use by other applications that expect 511a rigid namespace mapping. 512 513=over 4 514 515=item C<< $model->registerNamespace($prefix,$namespace_url); >> 516 517Register C<$prefix> to stand for the namespace C<$namespace_url>. 518This prefix can then be used to create nodes in constructors and Document methods. 519It will also be recognized in XPath expressions. 520 521=item C<< $model->getNamespacePrefix($namespace,$forattribute,$probe); >> 522 523Return the prefix to use for the given C<$namespace>. 524If C<$forattribute> is nonzero, then it looks up the prefix as appropriate for attributes. 525If C<$probe> is nonzero, it only probes for the prefix, without creating a missing entry. 526 527=item C<< $model->getNamespace($prefix,$probe); >> 528 529Return the namespace url for the given C<$prefix>. 530 531=back 532 533=head2 Model queries 534 535=over 2 536 537=item C<< $boole = $model->canContain($tag,$childtag); >> 538 539Returns whether an element with qualified name C<$tag> can contain an element 540with qualified name C<$childtag>. 541The tag names #PCDATA, #Document, #Comment and #ProcessingInstruction 542are specially recognized. 543 544=item C<< $boole = $model->canHaveAttribute($tag,$attribute); >> 545 546Returns whether an element with qualified name C<$tag> is allowed to have an attribute 547with the given name. 548 549=back 550 551=head1 SEE ALSO 552 553L<LaTeXML::Common::Model::DTD>, 554L<LaTeXML::Common::Model::RelaxNG>. 555 556=head1 AUTHOR 557 558Bruce Miller <bruce.miller@nist.gov> 559 560=head1 COPYRIGHT 561 562Public domain software, produced as part of work done by the 563United States Government & not subject to copyright in the US. 564 565=cut 566