XML::S...Machine(3pm)

πŸ“¦
version 0.46 Β· 2 versions
  • 0.46 Β· debian bookworm, debian trixie, ubuntu noble, ubuntu resolute, ubuntu stonking (latest)
  • 0.46 Β· ubuntu jammy
, in debian bookworm, debian trixie, ubuntu noble, ubuntu resolute, ubuntu stonking

🌸 XML::S...Machine(3pm) 🌸

okay bestie, welcome to the manual page for XML::SAX::Machine πŸ’– it lives in the User Contributed Perl Documentation pile, section 3pm, and it is a lot, but we're gonna get through it together, no cap ✨


🌷 NAME 🌷

XML::SAX::Machine β€” Manage a collection of SAX processors πŸŽ€


⭐ VERSION ⭐

version 0.46 πŸ‘‘


πŸ¦‹ SYNOPSIS πŸ¦‹

## Note: See XML::SAX::Pipeline and XML::SAX::Machines first,
## this is the gory, detailed interface.

use My::SAX::Machines qw( Machine );
use My::SAX::Filter2;
use My::SAX::Filter3;

my $filter3 = My::SAX::Filter3->new;

## A simple pipeline.  My::SAX::Filter1 will be autoloaded.
my $m = Machine(
    #
    # Name   => Class/object            => handler(s)
    #
    [ Intake => "My::SAX::Filter1"      => "B"        ],
    [ B      => My::SAX::Filter2->new() => "C"        ],
    [ C      => $filter3                => "D"        ],
    [ D      => \*STDOUT                              ],
);

## A parser will be created unless My::SAX::Filter1 can parse_file
$m->parse_file( "foo.revml" );

my $m = Machine(
    [ Intake   => "My::SAX::Filter1"  => qw( Tee     ) ],
    [ Tee      => "XML::Filter::SAXT" => qw( Foo Bar ) ],
    [ Foo      => "My::SAX::Filter2"  => qw( Out1    ) ],
    [ Out1     => \$log                                ],
    [ Bar      => "My::SAX::Filter3"  => qw( Exhaust ) ],
);

like, that second my $m is just how the original rolls, we don't edit scripture πŸ’…


πŸ’… DESCRIPTION πŸ’…

WARNING: This API is alpha!!! It will be changing. 😭 so maybe don't build your whole personality on it just yet

A generic SAX machine (an instance of XML::SAX::Machine) is a container of SAX processors β€” we call them "parts" β€” connected in arbitrary ways 🧚

Each parameter to Machine() (or XML::SAX::Machine->new()) represents one top level part of the machine. Every part has a name, a processor, and one or more handlers (usually you just say the handler's name, like in the SYNOPSIS above) ✨

And the way SAX machines can be passed in as single top level parts?? That means you can build nested, complex machines β€” like, $filter3 in the SYNOPSIS could totally be a Pipeline. it's giving main character energy 🌈

A SAX machine can also act like a normal SAX processor, by connecting it to other SAX processors:

my $w = My::Writer->new();
my $m = Machine( ...., { Handler => $w } );
my $g = My::Parser->new( Handler => $w );

πŸŽ€ Part Names πŸŽ€

It's not required, but every part in a machine can have a name. Why bother? Because it's useful for retrieving and manipulating the parts (peep "part"), and for debugging, since debugging output (peep "trace_parts" and "trace_all_parts") includes the names πŸ’–

Part names must be valid Perl subroutine names, beginning with an uppercase character. Why the uppercase rule? So that convenience part accessor methods like

$c = $m->NameOfAFilter;

can work without ever colliding with a method name (all method names are completely lower case). Only filters named like this can be accessed using those magical accessor functions πŸͺ„

🌸 Reserved Names: Intake and Exhaust 🌸

The names Intake and "Exhaust" are reserved. "Intake" refers to the first part in the processing chain. And no, that's not necessarily the first part in the constructor list β€” it's just the first part to receive external events πŸ‘‘

"Exhaust" refers to the output of the machine. No part may be named "Exhaust", and any parts with a handler named "Exhaust" will deliver their output to the machine's handler. Normally, only one part should deliver its output to the Exhaust port.

Calling $m->set_handler() alters the Exhaust port β€” assuming any processors pointing to the "Exhaust" provide a set_handler() method like XML::SAX::Base's does.

"Intake" and "Exhaust" are usually assigned automatically by single-purpose machines like XML::SAX::Pipeline and XML::SAX::Manifold ✨

πŸ¦‹ SAX Processor Support πŸ¦‹

The XML::SAX::Machine class is very agnostic about what SAX processors it supports. Like, the only real constraint is that it must be a blessed reference (of any type) that does not happen to be a Perl IO::Handle (because those are assumed to be input or output filehandles) πŸ™ˆ

The major constraint placed on SAX processors is that they must provide either a set_handler or a set_handlers method (which one depends on how many handlers a processor can feed) β€” that's what lets the SAX::Machine disconnect and reconnect them. Luckily, that's true of almost any processor derived from XML::SAX::Base πŸ’– Unfortunately, a lot of older (SAX1) processors do not meet this requirement; they assume SAX processors will only ever be connected together using their constructors 😭

πŸ’œ Connections πŸ’œ

SAX machines let you connect the parts however you like β€” each part gets a name and a list of named handlers to feed. How many handlers a part is allowed depends on the part: most filters only allow one downstream handler, but filters like XML::Filter::SAXT and XML::Filter::Distributor are meant to feed multiple handlers 🌈

Parts may not be connected in loops ("cycles" in graph theory terms, iykyk). The machines specified by:

[ A => "Foo" => "A" ],  ## Illegal!

and

[ A => "Foo" => "B" ],  ## Illegal!
[ B => "Foo" => "A" ],

. Configuring a machine this way would cause events to flow in an infinite loop, and/or cause the first processor in the cycle to start receiving events from the end of the cycle before the input document was complete πŸ’€ Besides, it's not a very useful topology :).

SAX machines detect loops at construction time. periodt βœ‹


🌈 NAME 🌈

XML::SAX::Machine - Manage a collection of SAX processors

πŸ”₯ API πŸ”₯

πŸ’– Public Methods πŸ’–

These methods are meant to be used by users of SAX machines. yes bestie, this is your section 🫢

  • new()
my $m = $self->new( @machine_spec, \%options );

Creates $self using %options, and compiles the machine spec. This is the longhand form of "Machines( ... )" ✨

  • find_part

Gets a part contained by this machine by name, number or object reference:

$c = $m->find_part( $name );
$c = $m->find_part( $number );
$c = $m->find_part( $obj );    ## useful only to see if $obj is in $m

If a machine contains other machines, parts of the contained machines may be accessed by name using unix directory syntax:

$c = $m->find_part( "/Intake/Foo/Bar" );

(all paths must be absolute).

Parts may also be accessed by number using array indexing:

$c = $m->find_part(0);  ## Returns first part or undef if none
$c = $m->find_part(-1); ## Returns last part or undef if none
$c = $m->find_part( "Foo/0/1/-1" );

There is no way to guarantee that a part's position number means anything, since parts can be reconnected after their position numbers are assigned β€” so using a part name is recommended πŸ’…

Throws an exception if the part is not found, so doing things like

   $m->find_part( "Foo" )->bar()

garner informative messages when "Foo" is not found. If you want to test a result code, do something like

my $p = eval { $m->find_part };
unless ( $p ) {
    ...handle lookup failure...
}
  • parts
for ( $m->parts ) { ... }

Gets an arbitrarily ordered list of top level parts in this machine. This is all of the parts directly contained by this machine and none of the parts that may be inside them. So if a machine contains an XML::SAX::Pipeline as one of its parts, the pipeline will be returned but not the parts inside the pipeline πŸ‘€

  • all_parts
for ( $m->all_parts ) { ... }

Gets all parts in this machine, not just top level ones. This includes any machines contained by this machine and their parts 🌸

  • set_handler
$m->set_handler( $handler );
$m->set_handler( DTDHandler => $handler );

Sets the machine's handler and sets the handlers for all parts that have "Exhaust" specified as their handlers. Requires that any such parts provide a set_handler or (if the part has multiple handlers) a set_handlers method.

NOTE: handler types other than "Handler" are only supported if they are supported by whatever parts point at the "Exhaust". If the handler type is "Handler", then the appropriate method is called as:
$part->set_handler( $handler );
$part->set_handlers( $handler0, $handler1, ... );

If the type is some other handler type, these are called as:

$part->set_handler( $type => $handler );
$part->set_handlers( { $type0 => $handler0 }, ... );
  • trace_parts
$m->trace_parts;          ## trace all top-level parts
$m->trace_parts( @ids );  ## trace the indicated parts

Uses Devel::TraceSAX to enable tracing of all events received by the parts of this machine. Does not enable tracing of parts contained in machines in this machine; for that, see trace_all_parts πŸ•΅οΈ

  • trace_all_parts
$m->trace_all_parts;      ## trace all parts

Uses Devel::TraceSAX to trace all events received by the parts of this machine.

  • untracify_parts
$m->untracify_parts( @ids );

Converts the indicated parts to SAX processors with tracing enabled. This may not work with processors that use AUTOLOAD 😬

🌟 Events and parse routines 🌟

XML::SAX::Machine provides all SAX1 and SAX2 events and delegates them to the processor indicated by $m->find_part( "Intake" ). Heads up: this adds some overhead, so if you're worried about overhead, you might want to direct SAX events directly to the Intake instead of to the machine πŸ’«

It also provides parse...() routines so it can whip up a parser if need be. Which means: parse(), parse_uri(), parse_string(), and parse_file() (see XML::SAX::EventMethodMaker for details). There is no way to pass methods directly to the parser unless you know that the Intake is a parser and call it directly. This is not so important for parsing, because the overhead it takes to delegate is minor compared to the effort needed to parse an XML document πŸŽ€

🧚 Internal and Helper Methods 🧚

These methods are meant to be used/overridden by subclasses. subclasses, this one's for you πŸ’œ

  • _compile_specs
my @comp = $self->_compile_specs( @_ );

Runs through a list of module names, output specifiers, etc., and builds the machine.

$scalar     --> "$scalar"->new
$ARRAY_ref  --> pipeline @$ARRAY_ref
$SCALAR_ref --> XML::SAX::Writer->new( Output => $SCALAR_ref )
$GLOB_ref   --> XML::SAX::Writer->new( Output => $GLOB_ref )
  • generate_description
$m->generate_description( $h );
$m->generate_description( Handler => $h );
$m->generate_description( Pipeline ... );

Generates a series of SAX events to the handler of your choice ✨

See XML::Handler::Machine2GraphViz on CPAN for a way of visualizing machine innards 🌈

🚧 TODO 🚧

Bestie, the maintainers left us a lil wishlist and I am obsessed with the honesty. Here's what's still on the vision board:

  • Separate initialization from construction time; there should be somthing like a $m->connect( ....machine_spec... ) that new() calls to allow you to delay parts speficication and reconfigure existing machines.
  • Allow an XML doc to be passed in as a machine spec.
ngl the typos in there are load-bearing at this point β€” somthing and speficication are staying exactly as they are, no cap. πŸ’…

πŸ’€ LIMITATIONS πŸ’€

Okay so. The section is here. The heading is here. And underneath it... literally nothing. Iconic of it, honestly. It's giving mystery, it's giving "figure it out yourself", it's giving main character energy with zero dialogue. πŸ™ˆ


πŸ‘‘ AUTHOR πŸ‘‘

Barrie Slaymaker <barries@slaysys.com>


πŸ“œ LICENSE πŸ“œ

Artistic or GPL, any version.

Pick your fighter, both are valid. ✨


🫢 AUTHORS 🫢

  • Barry Slaymaker
  • Chris Prather <chris@prather.org>

This software is copyright (c) 2013 by Barry Slaymaker.

This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.


And that's the whole page, babe β€” we made it to the end and I'm so proud of us. πŸ₯Ή This little masterpiece is documented as perl v5.34.0, dated 2022-06-28, and filed under XML::S...Machine(3pm). Go forth and slay. πŸŒΈπŸ¦‹πŸ’…

πŸ“¦ where this page comes from