Steffen Beyer > Data-Locations-5.2-fixed > Data::Locations
Module Version: 5.2  


Data::Locations - magic insertion points in your data


Did you already encounter the problem that you had to produce some data in a particular order, but that some piece of the data was still unavailable at the point in the sequence where it belonged and where it should have been produced?

Did you also have to resort to cumbersome and tedious measures such as storing the first and the last part of your data separately, then producing the missing middle part, and finally putting it all together?

In this simple case, involving only one deferred insertion, you might still put up with this solution.

But if there is more than one deferred insertion, requiring the handling of many fragments of data, you will probably get annoyed and frustrated.

You might even have to struggle with limitations of the file system of your operating system, or handling so many files might considerably slow down your application due to excessive file input/output.

And if you don't know exactly beforehand how many deferred insertions there will be (if this depends dynamically on the data being processed), and/or if the pieces of data you need to insert need additional (nested) insertions themselves, things will get really tricky, messy and troublesome.

In such a case you might wonder if there wasn't an elegant solution to this problem.

This is where the "Data::Locations" module comes in: It handles such insertion points automatically for you, no matter how many and how deeply nested, purely in memory, requiring no (inherently slower) file input/output operations.

(The underlying operating system will automatically take care if the amount of data becomes too large to be handled fully in memory, though, by swapping out unneeded parts.)

Moreover, it also allows you to insert the same fragment of data into SEVERAL different places.

This increases space efficiency because the same data is stored in memory only once, but used multiple times.

Potential infinite recursion loops are detected automatically and refused.

In order to better understand the underlying concept, think of "Data::Locations" as virtual files with almost random access: You can write data to them, you can say "reserve some space here which I will fill in later", and continue writing data.

And you can of course also read from these virtual files, at any time, in order to see the data that a given virtual file currently contains.

When you are finished filling in all the different parts of your virtual file, you can write out its contents in flattened form to a physical, real file this time, or process it otherwise (purely in memory, if you wish).

You can also think of "Data::Locations" as bubbles and bubbles inside of other bubbles. You can inflate these bubbles in any arbitrary order you like through a straw (i.e., the bubble's object reference).

Note that this module handles your data completely transparently, which means that you can use it equally well for text AND binary data.

You might also be interested in knowing that this module and its concept have already been heavily used in the automatic code generation of large software projects.


  use Data::Locations;

      $toplocation = Data::Locations->new();
      $toplocation = Data::Locations->new($filename);
      $sublocation = $location->new();
      $sublocation = $location->new($filename);

      $filename = $location->filename();
      $oldfilename = $location->filename($newfilename);

      $flag = $location->toplevel();

      print $location @items;

      $location->printf($format, @items);
      printf $location $format, @items;


      $item = $location->read();
      $item = <$location>;
      @list = $location->read();
      @list = <$location>;



      $ok = $location->dump();
      $ok = $location->dump($filename);


      tie(  *FILEHANDLE,  "Data::Locations", $location);
      tie(*{$filehandle}, "Data::Locations", $location);

      $location = tied   *FILEHANDLE;
      $location = tied *{$filehandle};

      untie   *FILEHANDLE;
      untie *{$filehandle};

      $filehandle = select();
      $oldfilehandle = select($newlocation);


Although "Data::Locations" behave like normal Perl file handles in most circumstances, opening a location with "open()" should be avoided: The corresponding file or pipe will actually be created, but subsequently data will nevertheless be sent to (and read from) the given location instead.

There is also no need to switch from read to write mode (or vice-versa), since locations are ALWAYS open BOTH for reading AND writing.

If you want to rewind a location to start reading at the beginning again (usually achieved by closing and re-opening a file), use the method "reset()" instead (see farther below for a description).

Likewise, closing a location with "close()" will have no other effect than to produce a warning message (under Perl 5.005 and if the "-w" switch is set) saying "operation ignored", and should therefore be avoided, too.

"Data::Locations" are rather delicate objects; they are valid Perl file handles AS WELL AS valid Perl objects AT THE SAME TIME.

As a consequence, YOU CANNOT INHERIT from the "Data::Locations" class, i.e., it is NOT possible to create a derived class or subclass of the "Data::Locations" class!

Trying to do so will cause many severe malfunctions, most of which will not be apparent immediately.

Chances are also great that by adding new attributes to a "Data::Locations" object you will clobber its (quite tricky) data structure.

Therefore, use embedding and delegation instead, rather than inheritance, as shown below:

  package My::Class;
  use Data::Locations;
  use Carp;

  sub new
      my($self) = shift;
      if (ref($self)) { $location = $self->{'delegate'}->new(); }
      else            { $location =     Data::Locations->new(); }
      $object =
              'delegate'   => $location,
              'attribute1' => $whatever,  ##  add your own
              'attribute2' => $whatelse
      bless($object, ref($self) || $self || __PACKAGE__);

      $AUTOLOAD =~ s/^.*:://;
      return if ($AUTOLOAD eq 'DESTROY');
      $AUTOLOAD = 'Data::Locations::' . $AUTOLOAD;
      if (defined &$AUTOLOAD)
          for ( $i = 0; $i < @_; $i++ )
              if (ref($_[$i]) eq __PACKAGE__)
                  $args[$i] = $_[$i]->{'delegate'};
                  $args[$i] = $_[$i];
          @_ = @args;
          goto &$AUTOLOAD;
          croak("$AUTOLOAD(): no such method");


Note that using this scheme, all methods available for "Data::Locations" objects are also (automatically and directly) available for "My::Class" objects, i.e.,

  use My::Class;
  $obj = My::Class->new();
  $obj->print("This is ");
  $sub = $obj->new();
  @items = $obj->read();
  print "<", join('|', @items), ">\n";
  $sub->print("an additional piece of ");
  @items = $obj->read();
  print "<", join('|', @items), ">\n";

will work as expected (unless you redefine these methods in "My::Class").

Moreover, with this scheme, you are free to add new methods and/or attributes as you please.

The class "My::Class" can also be subclassed without any restrictions.

However, "My::Class" objects are NOT valid Perl file handles; therefore, they cannot be used as such in combination with Perl's built-in operators for file access.



  #!/sw/bin/perl -w

  use Data::Locations;

  use strict;
  no strict "vars";

  $head = Data::Locations->new();  ##  E.g. for interface definitions
  $body = Data::Locations->new();  ##  E.g. for implementation


  $common = $head->new();    ##  Embed a new location in "$head"
  $body->print($common);     ##  Embed this same location in "$body"

  ##  Create some more locations...

  $copyright = Data::Locations->new();
  $includes  = Data::Locations->new();
  $prototype = Data::Locations->new();

  ##  ...and embed them in location "$common":


  ##  Note that the above is just to show you an alternate
  ##  (but less efficient) way! Normally you would use:
  ##      $copyright = $common->new();
  ##      $includes  = $common->new();
  ##      $prototype = $common->new();

  $head->println(";");  ##  The final ";" after a function prototype
  $body->println();     ##  Just a newline after a function header

  $body->println('    printf("Hello, world!\n");');

  $includes->print("#include <");
  $library = $includes->new();     ##  Nesting even deeper still...

  $prototype->print("void hello(void)");

  $copyright->println("    Copyright (c) 1997, 1998, 1999 by Steffen Beyer.");
  $copyright->println("    All rights reserved.");




  print "default filename = '", $copyright->filename(), "'\n";



When executed, this example will print

      Copyright (c) 1997, 1998, 1999 by Steffen Beyer.
      All rights reserved.
  default filename = 'default.txt'

to the screen and create the following two files:

      Copyright (c) 1997, 1998, 1999 by Steffen Beyer.
      All rights reserved.
  #include <stdio.h>
  void hello(void)
      printf("Hello, world!\n");

      Copyright (c) 1997, 1998, 1999 by Steffen Beyer.
      All rights reserved.
  #include <stdio.h>
  void hello(void);


  #!/sw/bin/perl -w

  use Data::Locations;

  use strict;
  no strict "vars";

  $html = Data::Locations->new("example.html");

  $head = $html->new();
  $body = $html->new();

  $tohead = $head->new();

  $tobody = $body->new();

  $title = $tohead->new();

  $tohead->print('<META NAME="description" CONTENT="');
  $description = $tohead->new();

  $tohead->print('<META NAME="keywords" CONTENT="');
  $keywords = $tohead->new();


  $tobody->print($title);      ##  Re-using this location!!

  $contents = $tobody->new();


  $title->print("'Data::Locations' Example HTML-Page");

  $description->println("Example for generating HTML pages");
  $description->print("using 'Data::Locations'");

  $keywords->print("locations, magic, insertion points,\n");
  $keywords->print("nested, recursive");

  $contents->println("This page was generated using the");
  $contents->println("module for Perl.");


When executed, this example will produce the following file ("example.html"):

  <TITLE>'Data::Locations' Example HTML-Page</TITLE>
  <META NAME="description" CONTENT="Example for generating HTML pages
  using 'Data::Locations'">
  <META NAME="keywords" CONTENT="locations, magic, insertion points,
  nested, recursive">
  <H1>'Data::Locations' Example HTML-Page</H1>
  This page was generated using the
  module for Perl.


  #!/sw/bin/perl -w

  ##  Note that this example only works as described if the "-w" switch
  ##  is set!

  package Non::Sense;

  ##  (This is to demonstrate that this example works with ANY package)

  use Data::Locations;
  use FileHandle;

  use strict;
  use vars qw($level0 $level1 $level2 $level3 $fh $fake);

  ##  Create the topmost location:

  $level0 = Data::Locations->new("level0.txt");

  print $level0 <<'VERBATIM';
  Printing first line to location 'level0' via OPERATOR 'print'.

  ##  Create an embedded location (nested 1 level deep):

  $level1 = $level0->new();

  Printing last line to location 'level0' via METHOD 'print'.

  ##  Now "tie" the embedded location to file handle STDOUT:


  print "Printing to location 'level1' via STDOUT.\n";

  ##  Create another location (which will be embedded later):

  $level2 = Data::Locations->new();

  ##  Create a file handle ("IO::Handle" works equally well):

  $fh = FileHandle->new();

  ##  Now "tie" the location "$level2" to this file handle "$fh":


  ##  And select "$fh" as the default output file handle:


  print "Printing to location 'level2' via default file handle '\$fh'.\n";

  ##  Embed location "$level2" in location "$level1":

  print $level1 $level2;

  ##  (Automatically removes "toplevel" status from location "$level2")

  print STDOUT "Printing to location 'level1' explicitly via STDOUT.\n";

  ##  Create a third embedded location (nested 3 levels deep):

  $level3 = $level2->new();

  ##  Restore STDOUT as the default output file handle:


  print $fh "Printing to location 'level2' via file handle '\$fh'.\n";

  ##  Trap all warnings:

  $SIG{__WARN__} = sub
      print STDERR "WARNING intercepted:\n", @_, "End Of Warning.\n";

  ##  Note that WITHOUT this trap, warnings would go to the system
  ##  standard error device DIRECTLY, WITHOUT passing through the
  ##  file handle STDERR!

  ##  Now "tie" location "$level3" to file handle STDERR:


  ##  Provoke a warning message (don't forget the "-w" switch!):

  $fake = \$fh;

  ##  Provoke another warning message (don't forget the "-w" switch!):


      ##  Silence warning that reference count of location is still > 0:

      local($^W) = 0;

      ##  And untie file handle STDOUT from location "$level1":

      untie *STDOUT;

  print "Now STDOUT goes to the screen again.\n";

  ##  Read from location "$level3":

  while (<STDERR>)  ##  Copy warning messages to the screen:
      if (/^.*?\bData::Locations::[a-z]+\(\):\s+(.+?)(?=\s+at\s|\n)/)
          print "Warning: $1\n";

  while (<STDERR>) { print; }

  ##  (Prints nothing because location was already read past its end)

  ##  Reset the internal reading mark:

  (tied *{STDERR})->reset();

  ##  (You should usually use "$level3->reset();", though!)

  while (<STDERR>) { print; }

  ##  (Copies the contents of location "$level3" to the screen)

  ##  Write output file "level0.txt":


When running this example, the following text will be printed to the screen (provided that you did use the "-w" switch):

  Now STDOUT goes to the screen again.
  Warning: REF reference ignored
  Warning: filename missing or empty
  WARNING intercepted:
  Data::Locations::print(): REF reference ignored at test.pl line 92
  End Of Warning.
  WARNING intercepted:
  Data::Locations::dump(): filename missing or empty at test.pl line 96
  End Of Warning.

The example also produces an output file named "level0.txt" with the following contents:

  Printing first line to location 'level0' via OPERATOR 'print'.
  Printing to location 'level1' via STDOUT.
  Printing to location 'level2' via default file handle '$fh'.
  WARNING intercepted:
  Data::Locations::print(): REF reference ignored at test.pl line 92
  End Of Warning.
  WARNING intercepted:
  Data::Locations::dump(): filename missing or empty at test.pl line 96
  End Of Warning.
  Printing to location 'level2' via file handle '$fh'.
  Printing to location 'level1' explicitly via STDOUT.
  Printing last line to location 'level0' via METHOD 'print'.


  #!/sw/bin/perl -w

  use Data::Locations;

  use strict;
  no strict "vars";

  $loc = Data::Locations->new();

  print $loc "Thi";
  print $loc "s is an ex";
  print $loc "treme";
  print $loc "ly long and ted";
  print $loc "ious line o";
  print $loc "f text con";
  print $loc "taining on";
  print $loc "ly meaning";
  print $loc "less gibberish.";

  $string = '';

  $loc->traverse( sub { $string .= $_[0]; } );

  print "'$string'\n";


This example will print:

'This is an extremely long and tedious line of text containing only meaningless gibberish.'


perl(1), perldata(1), perlfunc(1), perlsub(1), perlmod(1), perlref(1), perlobj(1), perlbot(1), perltoot(1), perltie(1), printf(3), sprintf(3).


This man page documents "Data::Locations" version 5.2.


  Steffen Beyer
  Ainmillerstr. 5 / App. 513
  D-80801 Munich


  (Contact by e-mail preferred)


Copyright (c) 1997, 1998, 1999 by Steffen Beyer. All rights reserved.


This package is free software; you can redistribute it and/or modify it under the same terms as Perl itself, i.e., under the terms of the "Artistic License" or the "GNU General Public License".

Please refer to the files "Artistic.txt" and "GNU_GPL.txt" in this distribution for details.


This package is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the "GNU General Public License" for more details.