dummy_foundation/lib/XML/XQL/DirXQL.pm
changeset 0 02cd6b52f378
equal deleted inserted replaced
-1:000000000000 0:02cd6b52f378
       
     1 # Attibute Definitions:
       
     2 #
       
     3 #  name: Text - name of file, dir, ...
       
     4 #  ext: Text - file extension
       
     5 #  no_ext: Text - name without ext
       
     6 #  full: Text - full path name
       
     7 #  abs: Text - absolute path name
       
     8 #
       
     9 #  M,age: Number - Age of file (in days)
       
    10 #                   [since script started says man(perlfunc)??]
       
    11 #  cre,create: Date (see age)
       
    12 #  A,acc_in_days: Number - Last access time in days
       
    13 #  acc,access: Date (see A)
       
    14 #    set with utime()
       
    15 #  f,is_file: Boolean
       
    16 #  d,is_dir: Boolean
       
    17 #  l,is_link: Boolean
       
    18 #  p,is_pipe: Boolean
       
    19 #  e,exists: Boolean
       
    20 #  z,is_zero: Boolean - whether size equals zero bytes
       
    21 #  r,readable: Boolean
       
    22 #  w,writable: Boolean
       
    23 #  x,executable: Boolean
       
    24 #  o,owned: Boolean - whether it is owned (by effective uid)
       
    25 #
       
    26 #---------------------------------------------------------------------------
       
    27 # Todo: 
       
    28 # - implement abs(): absolute filepath
       
    29 # - support links: use lstat(), @link 
       
    30 # - flags: -R,-W,-X,-O (by real uid/gid instead of effective uid,
       
    31 #          -S (is_socket), -b (block special file), -c (char. special file),
       
    32 #          -t  Filehandle is opened to a tty.
       
    33 #          -u  File has setuid bit set.
       
    34 #          -g  File has setgid bit set.
       
    35 #          -k  File has sticky bit set.
       
    36 #          -T  File is a text file.
       
    37 #          -B  File is a binary file (opposite of -T).
       
    38 #          -C  inode change time in days.
       
    39 #              set with utime() ??
       
    40 #
       
    41 # stat() fields:
       
    42 #
       
    43 #         0 dev      device number of filesystem
       
    44 #         1 ino      inode number
       
    45 #         2 mode     file mode  (type and permissions)
       
    46 #	    add mode_str ??: "rwxr-xr--"
       
    47 #         3 nlink    number of (hard) links to the file
       
    48 #         4 uid      numeric user ID of file's owner
       
    49 #           add uname
       
    50 #         5 gid      numeric group ID of file's owner
       
    51 #           add gname
       
    52 #         6 rdev     the device identifier (special files only)
       
    53 # x       7 size     total size of file, in bytes
       
    54 # -       8 atime    last access time since the epoch
       
    55 # -       9 mtime    last modify time since the epoch
       
    56 # -      10 ctime    inode change time (NOT creation time!) since the epoch
       
    57 #        11 blksize  preferred block size for file system I/O
       
    58 #        12 blocks   actual number of blocks allocated
       
    59 
       
    60 package XML::XQL::DirXQL;
       
    61 
       
    62 use strict;
       
    63 use XML::XQL;
       
    64 use XML::XQL::Date;
       
    65 
       
    66 sub dirxql
       
    67 {
       
    68     my ($context, $list, $filepath) = @_;
       
    69 
       
    70     $filepath = XML::XQL::toList ($filepath->solve ($context, $list));
       
    71     my @result;
       
    72     for my $file (@$filepath)
       
    73     {
       
    74 	push @result, XML::XQL::DirDoc->new (Root => $file->xql_toString)->root;
       
    75     }
       
    76     \@result;
       
    77 }
       
    78 
       
    79 XML::XQL::defineFunction ("dirxql", \&XML::XQL::DirXQL::dirxql, 1, 1);
       
    80 
       
    81 package XML::XQL::DirNode;
       
    82 # extended by: DirDoc, DirAttr, DirElem (File, Dir), FileContents
       
    83 
       
    84 use vars qw{ @ISA $SEP };
       
    85 @ISA = qw{ XML::XQL::Node };
       
    86 
       
    87 # Directory path separator (default: Unix)
       
    88 $SEP = "/";
       
    89 
       
    90 if ((defined $^O and
       
    91      $^O =~ /MSWin32/i ||
       
    92      $^O =~ /Windows_95/i ||
       
    93      $^O =~ /Windows_NT/i) ||
       
    94     (defined $ENV{OS} and
       
    95      $ENV{OS} =~ /MSWin32/i ||
       
    96      $ENV{OS} =~ /Windows_95/i ||
       
    97      $ENV{OS} =~ /Windows_NT/i))
       
    98 {
       
    99     $SEP = "\\";	# Win32
       
   100 }
       
   101 elsif  ((defined $^O and $^O =~ /MacOS/i) ||
       
   102 	(defined $ENV{OS} and $ENV{OS} =~ /MacOS/i))
       
   103 {
       
   104     $SEP = ":";		# Mac
       
   105 }
       
   106 
       
   107 sub isElementNode { 0 }
       
   108 sub isTextNode    { 0 }
       
   109 sub xql_parent    { $_[0]->{Parent} }
       
   110 #sub xql_document { $_[0]->{Doc} }
       
   111 sub xml_xqlString { $_[0]->toString }
       
   112 
       
   113 sub xql
       
   114 {
       
   115     my $self = shift;
       
   116 
       
   117     # Odd number of args, assume first is XQL expression without 'Expr' key
       
   118     unshift @_, 'Expr' if (@_ % 2 == 1);
       
   119     my $query = new XML::XQL::Query (@_);
       
   120     $query->solve ($self);
       
   121 }
       
   122 
       
   123 sub xql_sortKey
       
   124 {
       
   125     my $key = $_[0]->{SortKey};
       
   126     return $key if defined $key;
       
   127 
       
   128     $key = XML::XQL::createSortKey ($_[0]->{Parent}->xql_sortKey, 
       
   129 				    $_[0]->xql_childIndex, 1);
       
   130 #print "xql_sortKey $_[0] ind=" . $_[0]->xql_childIndex . " key=$key str=" . XML::XQL::keyStr($key) . "\n";
       
   131     $_[0]->{SortKey} = $key;
       
   132 }
       
   133 
       
   134 sub xql_node
       
   135 {
       
   136     my $self = shift;
       
   137     $self->build unless $self->{Built};
       
   138 
       
   139     $self->{C};
       
   140 }
       
   141 
       
   142 sub getChildIndex
       
   143 {
       
   144     my ($self, $kid) = @_;
       
   145     my $i = 0;
       
   146     for (@{ $self->xql_node })
       
   147     {
       
   148 	return $i if $kid == $_;
       
   149 	$i++;
       
   150     }
       
   151     return -1;
       
   152 }
       
   153 
       
   154 sub xql_childIndex
       
   155 {
       
   156     $_[0]->{Parent}->getChildIndex ($_[0]);
       
   157 }
       
   158 
       
   159 # As it appears in the XML document
       
   160 sub xql_xmlString
       
   161 {
       
   162     $_[0]->toString;
       
   163 #?? impl.
       
   164 }
       
   165 
       
   166 sub create_date_from_days
       
   167 {
       
   168     my ($days, $srcNode) = @_;
       
   169     my $secs = int (0.5 + $days * 24 * 3600 );
       
   170 
       
   171     my $internal = Date::Manip::DateCalc ("today", "- $secs seconds");
       
   172 
       
   173     new XML::XQL::Date (SourceNode => $srcNode,
       
   174 			Internal => $internal,
       
   175 			String => $internal );
       
   176 }
       
   177 
       
   178 #------ WHITESPACE STUFF (DELETE??)
       
   179 
       
   180 # Find previous sibling that is not a text node with ignorable whitespace
       
   181 sub xql_prevNonWS
       
   182 {
       
   183     my $self = shift;
       
   184     my $parent = $self->{Parent};
       
   185     return unless $parent;
       
   186 
       
   187     for (my $i = $parent->getChildIndex ($self) - 1; $i >= 0; $i--)
       
   188     {
       
   189 	my $node = $parent->getChildAtIndex ($i);
       
   190 	return $node unless $node->xql_isIgnorableWS;	# skip whitespace
       
   191     }
       
   192     undef;
       
   193 }
       
   194 
       
   195 # True if it's a Text node with just whitespace and xml::space != "preserve"
       
   196 sub xql_isIgnorableWS
       
   197 {
       
   198     0;
       
   199 }
       
   200 
       
   201 # Whether the node should preserve whitespace
       
   202 # It should if it has attribute xml:space="preserve"
       
   203 sub xql_preserveSpace
       
   204 {
       
   205     $_[0]->{Parent}->xql_preserveSpace;
       
   206 }
       
   207 
       
   208 #---------------------------------------------------------------------------
       
   209 package XML::XQL::DirDoc;		# The Document
       
   210 use vars qw{ @ISA };
       
   211 @ISA = qw{ XML::XQL::DirNode };
       
   212 
       
   213 sub new
       
   214 {
       
   215     my ($type, %hash) = @_;
       
   216     my $self = bless \%hash, $type;
       
   217 
       
   218     $self->{Root} = "." unless exists $self->{Root};
       
   219 
       
   220     my $dirname;
       
   221     if ($self->{Root} =~ /^(.+)\Q${XML::XQL::DirNode::SEP}\E(.+)$/)
       
   222     {
       
   223 	$self->{Prefix} = $1;
       
   224 	$dirname = $2;
       
   225     }
       
   226     else
       
   227     {
       
   228 	$self->{Prefix} = "";
       
   229 	$dirname = $self->{Root};
       
   230     }
       
   231 
       
   232     $self->{Dir} = new XML::XQL::Dir (TagName => $dirname, Parent => $self);
       
   233     $self->{Built} = 1;
       
   234 
       
   235     return $self;
       
   236 }
       
   237 
       
   238 sub xql
       
   239 {
       
   240     shift->root->xql (@_);
       
   241 }
       
   242 
       
   243 sub root           { $_[0]->{Dir} }
       
   244 
       
   245 sub isElementNode  { 0 }
       
   246 sub xql_nodeType   { 9 }
       
   247 sub xql_childCount { 1 }
       
   248 sub fullname       { $_[0]->{Prefix} }
       
   249 sub xql_sortKey    { "" }
       
   250 sub xql_parent     { undef }
       
   251 sub xql_nodeName   { "#document" }
       
   252 sub depth          { 0 }
       
   253 sub xql_node       { [ $_[0]->{Dir} ] }
       
   254 
       
   255 sub xql_element
       
   256 {
       
   257     my ($self, $elem) = @_;
       
   258 
       
   259     my $dir = $self->{Dir};
       
   260     if (defined $elem)
       
   261     {
       
   262 	return [ $dir ] if $dir->{TagName} eq $elem;
       
   263     }
       
   264     else
       
   265     {
       
   266 	return [ $dir ];
       
   267     }
       
   268 }
       
   269 
       
   270 # By default the elements in a document don't preserve whitespace
       
   271 sub xql_preserveSpace
       
   272 {
       
   273     0;
       
   274 }
       
   275 
       
   276 sub toString
       
   277 {
       
   278     $_[0]->root->toString;
       
   279 }
       
   280 
       
   281 #----------------------------------------------------------------------------
       
   282 package XML::XQL::DirAttrDef;	# Definitions for DirAttr nodes
       
   283 
       
   284 sub new
       
   285 {
       
   286     my ($type, %hash) = @_;
       
   287     bless \%hash, $type;
       
   288 }
       
   289 
       
   290 sub dump
       
   291 {
       
   292     print $_[0]->toString . "\n";
       
   293 }
       
   294 
       
   295 sub toString
       
   296 {
       
   297     my $self = shift;
       
   298     print "DirAttrDef $self\n";
       
   299     my $i = 0;
       
   300     for my $attrName ($self->in_order)
       
   301     {
       
   302 	my $a = $self->{$attrName};
       
   303 	print "[$i] name=$attrName"; $i++;
       
   304 	print " order=" . $a->{Order};
       
   305 	print " get=" . $a->{Get} if defined $a->{Get};
       
   306 	print " set=" . $a->{Set} if defined $a->{Set};
       
   307 	if (defined $a->{Alias})
       
   308 	{
       
   309 	    print " alias=" . join (",", @{ $a->{Alias} });
       
   310 	}
       
   311 	print "\n";
       
   312     }
       
   313     if (defined $self->{'@ALIAS'})
       
   314     {
       
   315 	print "Alias: ";
       
   316 	my $alias = $self->{'@ALIAS'};
       
   317 	
       
   318 	print join (",", map { "$_=" . $alias->{$_} } keys %$alias);
       
   319 	print "\n";
       
   320     }
       
   321 }
       
   322 
       
   323 sub clone
       
   324 {
       
   325     my $self = shift;
       
   326     my $n = new XML::XQL::DirAttrDef;
       
   327     $n->{'@IN_ORDER'} = [ @{ $self->{'@IN_ORDER'} } ];
       
   328 
       
   329     for my $a (@{ $self->{'@IN_ORDER'} })
       
   330     {
       
   331 	$n->{$a} = { %{ $self->{$a} } };
       
   332 	$n->{$a}->{Alias} = [ @{ $self->{$a}->{Alias} } ]
       
   333 	    if defined $self->{$a}->{Alias};
       
   334     }
       
   335     $n->{'@ALIAS'} = { %{ $self->{'@ALIAS'} } }
       
   336 	    if defined $self->{'@ALIAS'};
       
   337 
       
   338     return $n;
       
   339 }
       
   340 
       
   341 sub in_order { defined $_[0]->{'@IN_ORDER'} ? @{ $_[0]->{'@IN_ORDER'} } : () }
       
   342 sub alias    { $_[0]->{'@ALIAS'}->{$_[1]} }
       
   343 sub order    { $_[0]->{$_[1]}->{Order} }
       
   344 sub get      { $_[0]->{$_[1]}->{Get} }
       
   345 sub set      { $_[0]->{$_[1]}->{Set} }
       
   346 
       
   347 sub remove_attr
       
   348 {
       
   349     my ($self, $name) = @_;
       
   350     next unless defined $self->{$name};
       
   351 
       
   352     my $order = $self->{$name}->{Order};
       
   353     my @in_order = $self->in_order;
       
   354     splice @in_order, $order, 1;
       
   355     
       
   356     # Reassign Order numbers
       
   357     for (my $i = 0; $i < @in_order; $i++)
       
   358     {
       
   359 	$self->{$name}->{Order} = $i;
       
   360     }
       
   361     $self->{'@IN_ORDER'} = \@in_order;
       
   362     
       
   363     delete $self->{$name};
       
   364 }
       
   365 
       
   366 sub define_attr
       
   367 {
       
   368     my ($self, %hash) = @_;
       
   369     my $name = $hash{Name};
       
   370 
       
   371     if (defined $self->{$name})
       
   372     {
       
   373 	$hash{Order} = $self->{$name}->{Order} unless defined $hash{Order};
       
   374 	$self->remove_attr ($name);
       
   375     }
       
   376 
       
   377     my @in_order = $self->in_order;
       
   378     $hash{Order} = -1
       
   379 	if $hash{Order} >= @in_order;
       
   380     
       
   381     if ($hash{Order} == -1)
       
   382     {
       
   383 	push @in_order, $name;
       
   384     }
       
   385     else
       
   386     {
       
   387 	splice @in_order, $hash{Order}, 0, $name;
       
   388     }
       
   389     $self->{$name} = \%hash;
       
   390 
       
   391     # Reassign Order numbers
       
   392     for (my $i = 0; $i < @in_order; $i++)
       
   393     {
       
   394 	$self->{$name}->{Order} = $i;
       
   395     }
       
   396     $self->{'@IN_ORDER'} = \@in_order;
       
   397 
       
   398     my @alias = defined $hash{Alias} ? @{ $hash{Alias} } : ();
       
   399     for (@alias)
       
   400     {
       
   401 	$self->{'@ALIAS'}->{$_} = $name;
       
   402     }
       
   403 }
       
   404 
       
   405 #----------------------------------------------------------------------------
       
   406 package XML::XQL::DirAttr;	# Attr node
       
   407 use vars qw{ @ISA %GET_ATTR_FUNC %SET_ATTR_FUNC };
       
   408 @ISA = qw{ XML::XQL::DirNode };
       
   409 
       
   410 sub new
       
   411 {
       
   412     my ($type, %hash) = @_;
       
   413     my $self = bless \%hash, $type;
       
   414     
       
   415     $self->{xql_value} = $self->{Parent}->{AttrDef}->get ($hash{Name});
       
   416     $self->{xql_setValue} = $self->{Parent}->{AttrDef}->set ($hash{Name});
       
   417     $self;
       
   418 }
       
   419 
       
   420 sub isElementNode  { 0 }
       
   421 sub xql_nodeType   { 2 }
       
   422 sub xql_nodeName   { $_[0]->{Name} }
       
   423 sub xql_childIndex { $_[0]->{Parent}->attrIndex ($_[0]->{Name}) }
       
   424 sub xql_childCount { 0 }
       
   425 sub xql_node       { [] }
       
   426 sub is_defined     { exists $_[0]->{Value} }
       
   427 
       
   428 sub create	{ XML::XQL::DirNode::create_date_from_days ($_[0]->{Parent}->age, $_[0]) }
       
   429 sub age		{ new XML::XQL::Number ($_[0]->{Parent}->age, $_[0]) }
       
   430 sub size	{ new XML::XQL::Text ($_[0]->{Parent}->size, $_[0]) }
       
   431 sub ext		{ new XML::XQL::Text ($_[0]->{Parent}->ext, $_[0]) }
       
   432 sub no_ext	{ new XML::XQL::Text ($_[0]->{Parent}->no_ext, $_[0]) }
       
   433 sub name	{ new XML::XQL::Text ($_[0]->{Parent}->name, $_[0]) }
       
   434 sub full	{ new XML::XQL::Text ($_[0]->{Parent}->full, $_[0]) }
       
   435 sub abs 	{ new XML::XQL::Text ($_[0]->{Parent}->abs, $_[0]) }
       
   436 sub is_file	{ new XML::XQL::Boolean ($_[0]->{Parent}->is_file, $_[0]) }
       
   437 sub is_dir	{ new XML::XQL::Boolean ($_[0]->{Parent}->is_dir, $_[0]) }
       
   438 sub is_link	{ new XML::XQL::Boolean ($_[0]->{Parent}->is_link, $_[0]) }
       
   439 sub is_pipe	{ new XML::XQL::Boolean ($_[0]->{Parent}->is_pipe, $_[0]) }
       
   440 sub it_exists	{ new XML::XQL::Boolean ($_[0]->{Parent}->it_exists, $_[0]) }
       
   441 sub is_zero	{ new XML::XQL::Boolean ($_[0]->{Parent}->is_zero, $_[0]) }
       
   442 sub readable	{ new XML::XQL::Boolean ($_[0]->{Parent}->readable, $_[0]) }
       
   443 sub writable	{ new XML::XQL::Boolean ($_[0]->{Parent}->writable, $_[0]) }
       
   444 sub executable	{ new XML::XQL::Boolean ($_[0]->{Parent}->executable, $_[0]) }
       
   445 sub owned	{ new XML::XQL::Boolean ($_[0]->{Parent}->owned, $_[0]) }
       
   446 
       
   447 sub last_access_in_days
       
   448 {
       
   449     new XML::XQL::Number ($_[0]->{Parent}->last_access_in_days, $_[0]);
       
   450 }
       
   451 
       
   452 sub last_access
       
   453 { 
       
   454   XML::XQL::DirNode::create_date_from_days ($_[0]->{Parent}->last_access_in_days, $_[0]);
       
   455 }
       
   456 
       
   457 sub toString       
       
   458 { 
       
   459     my $old = ""; #$_[0]->is_defined ? "" : " (undef)";
       
   460     my $val = $_[0]->xql_value->xql_toString; #exists $_[0]->{Value} ? $_[0]->{Value}->xql_toString : "(undef)";
       
   461     $_[0]->{Name} . "=\"$val$old\""
       
   462 #?? encodeAttrValue
       
   463 }
       
   464 
       
   465 sub xql_value
       
   466 {
       
   467     $_[0]->{Value} ||= &{ $_[0]->{xql_value} } (@_);
       
   468 }
       
   469 
       
   470 sub xql_setValue
       
   471 {
       
   472     my ($self, $text) = @_;
       
   473     my $set = $_[0]->{xql_setValue};
       
   474     if (defined $set)
       
   475     {
       
   476 	&$set ($self, $text);
       
   477     }
       
   478     else
       
   479     {
       
   480 	warn "xql_setValue not defined for DirAttr name=" . $self->{TagName};
       
   481     }
       
   482 }
       
   483 
       
   484 sub set_name
       
   485 {
       
   486     my ($attr, $text) = @_;
       
   487     $attr->{Parent}->set_name ($text);
       
   488 }
       
   489 
       
   490 sub set_ext
       
   491 {
       
   492     my ($attr, $text) = @_;
       
   493     $attr->{Parent}->set_ext ($text);
       
   494 }
       
   495 
       
   496 sub set_no_ext
       
   497 {
       
   498     my ($attr, $text) = @_;
       
   499     $attr->{Parent}->set_no_ext ($text);
       
   500 }
       
   501 
       
   502 #----------------------------------------------------------------------------
       
   503 package XML::XQL::DirElem;	# File or Dir
       
   504 use vars qw{ @ISA $ATTRDEF };
       
   505 @ISA = qw( XML::XQL::DirNode );
       
   506 
       
   507 $ATTRDEF = new XML::XQL::DirAttrDef;
       
   508 $ATTRDEF->define_attr (Name => 'name', Get => \&XML::XQL::DirAttr::name, 
       
   509 		       Set => \&XML::XQL::DirAttr::set_name);
       
   510 $ATTRDEF->define_attr (Name => 'full', Get => \&XML::XQL::DirAttr::full);
       
   511 $ATTRDEF->define_attr (Name => 'abs', Get => \&XML::XQL::DirAttr::abs);
       
   512 $ATTRDEF->define_attr (Name => 'no_ext', Get => \&XML::XQL::DirAttr::no_ext, 
       
   513 		       Set => \&XML::XQL::DirAttr::set_no_ext);
       
   514 $ATTRDEF->define_attr (Name => 'ext', Get => \&XML::XQL::DirAttr::ext, 
       
   515 		       Set => \&XML::XQL::DirAttr::set_ext);
       
   516 
       
   517 $ATTRDEF->define_attr (Name => 'age', Get => \&XML::XQL::DirAttr::age, 
       
   518 		       Alias => [ 'M' ] );
       
   519 $ATTRDEF->define_attr (Name => 'create', Get => \&XML::XQL::DirAttr::create, 
       
   520 		       Alias => [ 'cre' ] );
       
   521 $ATTRDEF->define_attr (Name => 'A', Get => \&XML::XQL::DirAttr::last_access_in_days,
       
   522 		       Alias => [ 'acc_in_days' ] );
       
   523 $ATTRDEF->define_attr (Name => 'access', Get => \&XML::XQL::DirAttr::last_access, 
       
   524 		       Alias => [ 'acc' ] );
       
   525 
       
   526 # These should only be implemented for Link and Pipe resp. !!
       
   527 $ATTRDEF->define_attr (Name => 'l', Get => \&XML::XQL::DirAttr::is_link, 
       
   528 		       Alias => [ 'is_link' ] );
       
   529 $ATTRDEF->define_attr (Name => 'p', Get => \&XML::XQL::DirAttr::is_pipe, 
       
   530 		       Alias => [ 'is_pipe' ] );
       
   531 
       
   532 $ATTRDEF->define_attr (Name => 'e', Get => \&XML::XQL::DirAttr::it_exists, 
       
   533 		       Alias => [ 'exists' ] );
       
   534 $ATTRDEF->define_attr (Name => 'z', Get => \&XML::XQL::DirAttr::is_zero, 
       
   535 		       Alias => [ 'is_zero' ] );
       
   536 $ATTRDEF->define_attr (Name => 'r', Get => \&XML::XQL::DirAttr::readable, 
       
   537 		       Alias => [ 'readable' ] );
       
   538 $ATTRDEF->define_attr (Name => 'w', Get => \&XML::XQL::DirAttr::writable, 
       
   539 		       Alias => [ 'writable' ] );
       
   540 $ATTRDEF->define_attr (Name => 'x', Get => \&XML::XQL::DirAttr::executable, 
       
   541 		       Alias => [ 'is_zero' ] );
       
   542 $ATTRDEF->define_attr (Name => 'o', Get => \&XML::XQL::DirAttr::owned, 
       
   543 		       Alias => [ 'owned' ] );
       
   544 
       
   545 #dump_attr_def();
       
   546 
       
   547 # mod => 0,
       
   548 # create => 1,
       
   549 # prot => 2,
       
   550 # protn => 3,
       
   551 # name => 4,
       
   552 # path => 5,
       
   553 # dir => 6,
       
   554 
       
   555 sub isElementNode   { 1 }
       
   556 sub xql_nodeType    { 1 }
       
   557 sub xql_nodeName    { $_[0]->{TagName} }
       
   558 
       
   559 sub dump_attr_def   { $ATTRDEF->dump; }
       
   560 sub attrNames       { @{ $_[0]->{AttrDef}->{'@IN_ORDER'} } }
       
   561 sub hasAttr         { exists $_[0]->{AttrDef}->{$_[1]} }
       
   562 
       
   563 # Attributes set/get
       
   564 sub full  		{ $_[0]->fullname }
       
   565 sub abs      		{ $_[0]->abs }
       
   566 sub no_ext		{ $_[0]->{TagName} }
       
   567 sub set_no_ext		{ shift->set_name (@_) }
       
   568 sub size		{ -s $_[0]->fullname }
       
   569 sub age			{ -M $_[0]->fullname }
       
   570 sub last_access_in_days	{ -A $_[0]->fullname }
       
   571 sub is_file             { -f $_[0]->fullname }
       
   572 sub is_dir              { -d $_[0]->fullname }
       
   573 sub is_link             { -l $_[0]->fullname }
       
   574 sub is_pipe             { -p $_[0]->fullname }
       
   575 sub it_exists           { -e $_[0]->fullname }
       
   576 sub is_zero             { -z $_[0]->fullname }
       
   577 sub readable            { -r $_[0]->fullname }
       
   578 sub writable            { -w $_[0]->fullname }
       
   579 sub executable          { -x $_[0]->fullname }
       
   580 sub owned               { -o $_[0]->fullname }
       
   581 
       
   582 sub attr_alias    
       
   583 {
       
   584     return undef unless defined $_[1];
       
   585 
       
   586     my $alias = $_[0]->{AttrDef}->alias ($_[1]);
       
   587     defined $alias ? $alias : $_[1];
       
   588 }
       
   589 
       
   590 sub create_path	# static
       
   591 {
       
   592     my ($dir, $file) = @_;
       
   593 
       
   594     if ($dir =~ /\Q${XML::XQL::DirNode::SEP}\E$/)
       
   595     {
       
   596 	return "$dir$file";
       
   597     }
       
   598     elsif ($dir eq "")	# e.g. when file is root directory '/'
       
   599     {
       
   600 	return $file;
       
   601     }
       
   602     else
       
   603     { 
       
   604 	return "$dir${XML::XQL::DirNode::SEP}$file";
       
   605     }
       
   606 }
       
   607 
       
   608 sub fullname
       
   609 { 
       
   610     my $pa = $_[0]->{Parent}->fullname;
       
   611     my $name = $_[0]->{TagName};
       
   612     create_path ($pa, $name);
       
   613 }
       
   614 
       
   615 #?? same as full name - for now
       
   616 sub abs
       
   617 {
       
   618     shift->fullname (@_);
       
   619 }
       
   620 
       
   621 sub parent_dir
       
   622 {
       
   623     $_[0]->{Parent}->fullname;
       
   624 }
       
   625 
       
   626 # With 3 params, sets the specified attribute with $attrName to $attrValue.
       
   627 # With 2 params, reinitializes the specified attribute with $attrName if
       
   628 # it currently has a value.
       
   629 
       
   630 sub update_attr
       
   631 {
       
   632     my ($self, $attrName, $attrValue) = @_;
       
   633 
       
   634     if (@_ == 3)
       
   635     {
       
   636 	my $attr = $self->getAttributeNode ($attrName);
       
   637 	if (defined $attr && defined $attr->{Value})
       
   638 	{
       
   639 	    $attr->{Value} = $attrValue;
       
   640 	}
       
   641     }
       
   642     else
       
   643     {
       
   644 	return unless exists $self->{A}->{$attrName};
       
   645 	my $a = $self->{A}->{$attrName};
       
   646 	if (exists $a->{Value})
       
   647 	{
       
   648 	    delete $a->{Value};
       
   649 	    $a->xql_value;	# reinitialize value
       
   650 	}
       
   651     }
       
   652 }
       
   653 
       
   654 sub set_name
       
   655 {
       
   656     my ($self, $text) = @_;
       
   657     my $fullName = $self->fullname;
       
   658     my $newName = create_path ($self->parent_dir, $text);
       
   659 
       
   660     if (rename ($fullName, $newName))
       
   661     {
       
   662 	$self->{TagName} = $text;
       
   663 	$self->update_attr ('name', $text);
       
   664 	$self->update_attr ('ext');
       
   665 	$self->update_attr ('no_ext');
       
   666 
       
   667 	return 1;
       
   668     }
       
   669     else
       
   670     {
       
   671 	warn "set_name: could not rename $fullName to $newName";
       
   672 	return 0;
       
   673     }
       
   674 }
       
   675 
       
   676 sub ext
       
   677 {
       
   678     my $name = $_[0]->{TagName};
       
   679     $name =~ /\.([^.]+)$/;
       
   680 #    print "ext name=$name ext=$1\n";
       
   681     return $1;
       
   682 }
       
   683 
       
   684 sub set_ext
       
   685 {
       
   686     my ($self, $text) = @_;
       
   687 #    print "set_ext $text\n";
       
   688     my $no_ext = $self->no_ext;
       
   689     $self->set_name (length ($text) ? "$no_ext.$text" : $no_ext);
       
   690 }
       
   691 
       
   692 sub no_ext
       
   693 {
       
   694     my $name = $_[0]->{TagName};
       
   695     $name =~ /^(.+)\.([^.]+)$/;
       
   696 #    print "no_ext name=$name no_ext=$1\n";
       
   697     return $1;
       
   698 }
       
   699 
       
   700 sub set_no_ext
       
   701 {
       
   702     my ($self, $text) = @_;
       
   703 #    print "set_no_ext $text\n";
       
   704     my $ext = $self->ext;
       
   705     $self->set_name (length ($ext) ? "$text.$ext" : $text);
       
   706 }
       
   707 
       
   708 sub xql_attribute
       
   709 {
       
   710     my ($node, $attrName) = @_;
       
   711     if (defined $attrName)
       
   712     {
       
   713 	my $attr = $node->getAttributeNode ($attrName);
       
   714 	defined ($attr) ? [ $attr ] : [];
       
   715     }
       
   716     else
       
   717     {
       
   718 	my @attr;
       
   719 	for my $name ($node->attrNames)
       
   720 	{
       
   721 	    push @attr, $node->getAttributeNode ($name);
       
   722 	}
       
   723 	\@attr;
       
   724     }
       
   725 }
       
   726 
       
   727 sub getAttributeNode
       
   728 {
       
   729     my ($self, $attrName) = @_;
       
   730     $attrName = $self->attr_alias ($attrName);
       
   731 
       
   732     return undef unless $self->hasAttr ($attrName);
       
   733 
       
   734     my $attr = $_[0]->{A}->{$attrName} ||= 
       
   735 	new XML::XQL::DirAttr (Parent => $self, Name => $attrName);
       
   736     $attr;
       
   737 }
       
   738 
       
   739 sub attrIndex
       
   740 {
       
   741     $_[0]->{AttrDef}->order ($_[1]);
       
   742 }
       
   743 
       
   744 sub toString       
       
   745 { 
       
   746     my ($self, $depth) = @_;
       
   747     my $indent = "  " x $depth;
       
   748     my $str = $indent;
       
   749     my $tagName = $self->{TagName};
       
   750 
       
   751     my $tfp = $self->tag_for_print;
       
   752 
       
   753     $str .= "<$tfp name=\"$tagName\"";
       
   754 
       
   755     for my $attrName ($self->attrNames)
       
   756     {
       
   757 	next unless exists $self->{A}->{$attrName};
       
   758 
       
   759 #?? don't print un-retrieved attributes - for now	
       
   760 	my $attr = $self->{A}->{$attrName};
       
   761 	next unless $attr->is_defined;
       
   762 	
       
   763 	$str .= " " . $attr->toString;
       
   764     }
       
   765 
       
   766     my $kids = $self->print_kids ? $self->xql_node : [];
       
   767     if (@$kids)
       
   768     {
       
   769 	$str .= ">\n";
       
   770 	for (@$kids)
       
   771 	{
       
   772 	    $str .= $_->toString ($depth + 1);
       
   773 	}
       
   774 	$str .= $indent . "</dir>\n";
       
   775     }
       
   776     else
       
   777     {
       
   778 	$str .= "/>\n";
       
   779     }
       
   780 }
       
   781 
       
   782 #----------------------------------------------------------------------------
       
   783 package XML::XQL::Dir;	# Element node
       
   784 use vars qw{ @ISA $ATTRDEF };
       
   785 @ISA = qw( XML::XQL::DirElem );
       
   786 
       
   787 $ATTRDEF = $XML::XQL::DirElem::ATTRDEF->clone;
       
   788 $ATTRDEF->define_attr (Name => 'd', Get => \&XML::XQL::DirAttr::is_dir, 
       
   789 		       Alias => [ 'is_dir' ] );
       
   790 #dump_attr_def();
       
   791 
       
   792 sub tag_for_print { "dir" }
       
   793 sub print_kids    { 1 }
       
   794 sub dump_attr_def { $ATTRDEF->dump }
       
   795 
       
   796 sub new
       
   797 {
       
   798     my ($type, %hash) = @_;
       
   799     $hash{AttrDef} = $ATTRDEF;
       
   800     bless \%hash, $type;
       
   801 }
       
   802 
       
   803 sub build
       
   804 {
       
   805     my ($self) = @_;
       
   806     my $dirname = $self->fullname;
       
   807 #    print "dirname=$dirname\n";
       
   808 
       
   809     if (opendir (DIR, $dirname))
       
   810     {
       
   811 	my @kids;
       
   812 
       
   813 	my @f = readdir (DIR);
       
   814 	closedir DIR;
       
   815 
       
   816 	for my $f (@f)
       
   817 	{
       
   818 	    next if $f =~ /^..?$/;
       
   819 #	    print "dirname=$dirname f=$f\n";
       
   820 
       
   821 	    my $full = defined $dirname ? "$dirname${XML::XQL::DirNode::SEP}$f" : $f;
       
   822 #	    print "dirname=$dirname full=$full\n";
       
   823 
       
   824 	    if (-f $full)
       
   825 	    {
       
   826 		push @kids, XML::XQL::File->new (Parent => $self, 
       
   827 						 TagName => $f
       
   828 						);
       
   829 	    }
       
   830 	    elsif (-d _)
       
   831 	    {
       
   832 		push @kids, XML::XQL::Dir->new (Parent => $self, 
       
   833 						TagName => $f
       
   834 					       );
       
   835 	    }
       
   836 	}
       
   837 	$self->{C} = \@kids;
       
   838 	$self->{Built} = 1;
       
   839     }
       
   840     else
       
   841     {
       
   842 	print "can't opendir $dirname: $!";
       
   843     }
       
   844 }
       
   845 
       
   846 sub xql_childCount
       
   847 {
       
   848     my $self = shift;
       
   849     $self->build unless $self->{Built};
       
   850     my $ch = $self->{C};
       
   851 
       
   852     defined $ch ? scalar(@$ch) : 0;
       
   853 }
       
   854 
       
   855 #----------------------------------------------------------------------------
       
   856 package XML::XQL::File;	# Element node
       
   857 use vars qw{ @ISA $ATTRDEF };
       
   858 @ISA = qw( XML::XQL::DirElem );
       
   859 
       
   860 $ATTRDEF = $XML::XQL::DirElem::ATTRDEF->clone;
       
   861 $ATTRDEF->define_attr (Name => 'f', Get => \&XML::XQL::DirAttr::is_file, 
       
   862 		       Alias => [ 'is_file' ] );
       
   863 $ATTRDEF->define_attr (Name => 'size', Get => \&XML::XQL::DirAttr::size, 
       
   864 		       Alias => [ 's' ]);
       
   865 #dump_attr_def();
       
   866 
       
   867 sub new
       
   868 {
       
   869     my ($type, %hash) = @_;
       
   870     $hash{AttrDef} = $ATTRDEF;
       
   871     bless \%hash, $type;
       
   872 }
       
   873 
       
   874 sub getChildIndex  { 0 }
       
   875 sub xql_childCount { 1 }
       
   876 sub contents       { $_[0]->build unless $_[0]->{Built}; $_[0]->{C}->[0] }
       
   877 sub xql_text       { $_[0]->contents->xql_text }
       
   878 sub xql_rawText    { $_[0]->contents->xql_text }
       
   879 sub tag_for_print  { "file" }
       
   880 sub print_kids     { 0 }
       
   881 sub dump_attr_def  { $ATTRDEF->dump }
       
   882 
       
   883 sub xql_rawTextBlocks
       
   884 {
       
   885     my $self = shift;
       
   886     ( [ 0, 0, $self->xql_text ])
       
   887 }
       
   888 
       
   889 sub xql_setValue
       
   890 {
       
   891     my ($self, $text) = @_;
       
   892     $self->contents->xql_setValue ($text);
       
   893 }
       
   894 
       
   895 sub xql_replaceBlockWithText
       
   896 {
       
   897     my ($self, $start, $end, $text) = @_;
       
   898     if ($start == 0 && $end == 0)
       
   899     {
       
   900 	$self->xql_setValue ($text);
       
   901     }
       
   902     else
       
   903     {
       
   904 	warn "xql_setText bad index start=$start end=$end";
       
   905     }
       
   906 }
       
   907 
       
   908 sub build
       
   909 {
       
   910     my $self = shift;
       
   911     push @{ $self->{C} }, XML::XQL::FileContents->new (Parent => $self);
       
   912     $self->{Built} = 1;
       
   913 }
       
   914 
       
   915 #----------------------------------------------------------------------------
       
   916 package XML::XQL::FileContents;	# Text node
       
   917 use vars qw{ @ISA };
       
   918 @ISA = qw{ XML::XQL::DirNode };
       
   919 
       
   920 sub new
       
   921 {
       
   922     my ($type, %hash) = @_;
       
   923     bless \%hash, $type;
       
   924 }
       
   925 
       
   926 sub isTextNode     { 1 }
       
   927 sub xql_nodeType   { 3 }
       
   928 sub xql_nodeName   { "#contents" }
       
   929 sub getChildIndex  { 0 }
       
   930 sub xql_childCount { 0 }
       
   931 sub xql_rawText    { $_[0]->xql_text }
       
   932 
       
   933 sub xql_text
       
   934 {
       
   935     my $self = shift;
       
   936     unless ($self->{Built})
       
   937     {
       
   938 	local *FILE;
       
   939 	local $/;	# slurp mode
       
   940 
       
   941 	if (open (FILE, $self->{Parent}->fullname))
       
   942 	{
       
   943 	    $self->{Data} = <FILE>;
       
   944 	    close FILE;
       
   945 	}
       
   946 	else
       
   947 	{
       
   948 #?? warning
       
   949 	}
       
   950 	$self->{Built} = 1;
       
   951     }
       
   952     $self->{Data};
       
   953 }
       
   954 
       
   955 sub xql_setValue
       
   956 {
       
   957     my ($self, $text) = @_;
       
   958 
       
   959     my $filename = $self->{Parent}->fullname;
       
   960     local *FILE;
       
   961     if (open (FILE, ">$filename"))
       
   962     {
       
   963 	print FILE $text;
       
   964 	$self->{Data} = $text;
       
   965 	$self->{Built} = 1;
       
   966 	close FILE;
       
   967     }
       
   968     else
       
   969     {
       
   970 	warn "xql_setValue could not open $filename for writing";
       
   971     }
       
   972 }
       
   973 
       
   974 return 1;