package XML::STX::Compiler;

require 5.005_02;
BEGIN { require warnings if $] >= 5.006; }
use strict;
use XML::STX::Base;
use XML::STX::Stylesheet;
use XML::STX::Buffer;
use Clone qw(clone);

@XML::STX::Compiler::ISA = qw(XML::STX::Base);

my $ATT_NUMBER = '\d+(\\.\d*)?|\\.\d+';
my $ATT_URIREF = '[a-z][\w\;\/\?\:\@\&\=\+\$\,\-\_\.\!\~\*\'\(\)\%]+';
my $ATT_STRING = '[\w][\w-]*';
my $ATT_NCNAME = '[A-Za-z_][\w\\.\\-]*';
my $ATT_QNAME  = "($ATT_NCNAME:)?$ATT_NCNAME";
my $ATT_QNAMES = "$ATT_QNAME( $ATT_QNAME)*";

# --------------------------------------------------

sub new {
    my $class = shift;
    my $options = ($#_ == 0) ? shift : { @_ };
    
    my $self = bless $options, $class;
    return $self;
}

# content ----------------------------------------

sub start_document {
    my $self = shift;

    $self->{e_stack} ||= [];
    $self->{g_stack} ||= [];
    $self->{nsc} ||= XML::NamespaceSupport->new({ xmlns => 1 });
}

sub end_document {
    my $self = shift;

    # post-processing
    $self->_process_templates($self->{Sheet}->{dGroup})
      if $self->{Sheet}->{alias}->[0];

    return $self->{Sheet};
}

sub start_element {
    my $self = shift;
    my $el = shift;

    #print "COMP: $el->{Name}\n";
    $self->doError(201, 3) if $self->{end};

    $el->{vars} = [];
    $self->{nsc}->pushContext;

    my $a = exists $el->{Attributes} ? $el->{Attributes} : {};
    my $e_stack_top = $#{$self->{e_stack}} == -1 ? undef 
      : $self->{e_stack}->[-1];
    my $g_stack_top = $#{$self->{g_stack}} == -1 ? undef 
      : $self->{g_stack}->[-1];

    # STX instructions ==================================================
    if (defined $el->{NamespaceURI} and $el->{NamespaceURI} eq STX_NS_URI) {

	# <stx:transform> ----------------------------------------
	if ($el->{LocalName} eq 'transform') {

	    $el->{LocalName} = 'group' if $self->{include};

	    if ($self->_allowed($el->{LocalName})) {

		# included module
		if ($self->{include}) {
		    #print "COMP: >include\n";
		    $el->{LocalName} = 'transform';

		    my $g = XML::STX::Group->new($self->{Sheet}->{next_gid},
						 $g_stack_top);
		    #print "COMP: >new group $self->{Sheet}->{next_gid} $g\n";

		    # the group is linked from the previous group
		    $g_stack_top->{groups}->{$self->{Sheet}->{next_gid}} = $g;

		    push @{$self->{g_stack}}, $g;
		    $self->{Sheet}->{next_gid}++;

		# principal module
		} else {
		    $self->{Sheet} = XML::STX::Stylesheet->new();
		    push @{$self->{g_stack}}, $self->{Sheet}->{dGroup};
		    #print "COMP: >new stylesheet $self->{Sheet}\n";
		    #print "COMP: >default group $self->{Sheet}->{dGroup}->{gid}\n";

		    $self->doError(212, 3, '<stx:transform>', 'version')
		      unless exists $el->{Attributes}->{'{}version'};

		    $self->doError(214, 3, 'version', '<stx:transform>', '1.0')
		      unless $el->{Attributes}->{'{}version'}->{Value} eq STX_VERSION;
		}

		# options: stxpath-default-namespace
		if (exists $a->{'{}stxpath-default-namespace'}) {
		    if ($a->{'{}stxpath-default-namespace'}->{Value} 
			=~ /^$ATT_URIREF$/) {
			push @{$self->{Sheet}->{Options}->
				 {'stxpath-default-namespace'}}, 
				   $a->{'{}stxpath-default-namespace'}->{Value};
		    } else {
			$self->doError(217, 3, 'stxpath-default-namespace', 
				       $a->{'{}stxpath-default-namespace'}->{Value},
				       'uri-reference', );	  
		    }
		}

		# options: output-encoding
		unless ($self->{include}) {
		    if (exists $a->{'{}output-encoding'}) {
			if ($a->{'{}output-encoding'}->{Value} 
			    =~ /^$ATT_STRING$/) {
			    $self->{Sheet}->{Options}->{'output-encoding'}
			      = $a->{'{}output-encoding'}->{Value};
			} else {
			    $self->doError(217, 3, 'output-encoding', 
					   $a->{'{}output-encoding'}->{Value},
					   'string');	  
			}
		    }
		}
		
		# options: recognize-cdata
 		if (exists $a->{'{}recognize-cdata'}) {
 		    if ($a->{'{}recognize-cdata'}->{Value} eq 'no') {
 			$self->{g_stack}->[-1]->{Options}->{'recognize-cdata'} = 0
 		    } elsif ($a->{'{}recognize-cdata'}->{Value} ne 'yes') {
 			$self->doError(205, 3, 'recognize-data', 
 				       $a->{'{}recognize-cdata'}->{Value});
 		    }
 		}

		# options: pass-through
 		if (exists $a->{'{}pass-through'}) {
 		    if ($a->{'{}pass-through'}->{Value} eq 'all') {
 			$self->{g_stack}->[-1]->{Options}->{'pass-through'} = 1

 		    } elsif ($a->{'{}pass-through'}->{Value} eq 'text') {
 			$self->{g_stack}->[-1]->{Options}->{'pass-through'} = 2

 		    } elsif ($a->{'{}pass-through'}->{Value} ne 'none') {
 			$self->doError(206, 3, 
 				       $a->{'{}pass-through'}->{Value});
 		    }
 		}

		# options: strip-space
 		if (exists $a->{'{}strip-space'}) {
 		    if ($a->{'{}strip-space'}->{Value} eq 'yes') {
 			$self->{g_stack}->[-1]->{Options}->{'strip-space'} = 1
 		    } elsif ($a->{'{}strip-space'}->{Value} ne 'no') {
 			$self->doError(205, 3, 'strip-space',
 				       $a->{'{}strip-space'}->{Value});
 		    }
 		}

	    }

	# <stx:include> ----------------------------------------
	} elsif ($el->{LocalName} eq 'include') {

	    if ($self->_allowed($el->{LocalName})) {
		
		$self->doError(212, 3, '<stx:include>', 'href')
		  unless exists $a->{'{}href'};

		$self->doError(214,3,'href','<stx:include>', 'URI reference') 
		  unless $a->{'{}href'}->{Value} =~ /^$ATT_URIREF$/;

		my $source = $self->{URIResolver}->resolve($a->{'{}href'}->{Value},
							   $self->{URI});

		# nested compiler inherits properties from the current one
 		my $iComp = XML::STX::Compiler->new({include => 1});
 		$iComp->{Sheet} = $self->{Sheet};
		$iComp->{e_stack} = $self->{e_stack};
		$iComp->{g_stack} = $self->{g_stack};
		$iComp->{nsc} = $self->{nsc};
 		$iComp->{DBG} = $self->{DBG};
 		$iComp->{URIResolver} = $self->{URIResolver};
 		$iComp->{ErrorListener} = $self->{ErrorListener};
 		$iComp->{URI} = $self->{URI};

 		$source->{XMLReader}->{Handler} = $iComp;
 		$source->{XMLReader}->parse_uri($source->{SystemId});
	    }

	# <stx:namespace-alias> ----------------------------------------
	} elsif ($el->{LocalName} eq 'namespace-alias') {

	    if ($self->_allowed($el->{LocalName})) {

		# --- stylesheet-prefix ---
		$self->doError(212, 3, '<stx:namespace-alias>', 'stylesheet-prefix')
		  unless exists $a->{'{}stylesheet-prefix'};

		$self->doError(214, 3, 'stylesheet-prefix',
			       '<stx:namespace-alias>', 'NCName')
		  unless $a->{'{}stylesheet-prefix'}->{Value} =~ /^$ATT_NCNAME$/
		    or $a->{'{}stylesheet-prefix'}->{Value} eq '#default';

		my $pre1 = $a->{'{}stylesheet-prefix'}->{Value} eq '#default' 
		  ? '' : $a->{'{}stylesheet-prefix'}->{Value};
		my $ns1 = $self->{nsc}->get_uri($pre1);
		#print "COMP: ns-alias> $pre1:$ns1\n";

		$self->doError(221, 3, $a->{'{}stylesheet-prefix'}->{Value}, 
			       'stx:namespace-alias') unless $ns1;

		# --- result-prefix ---
		$self->doError(212, 3, '<stx:namespace-alias>', 'result-prefix')
		  unless exists $a->{'{}result-prefix'};

		$self->doError(214, 3, 'result-prefix',
			       '<stx:namespace-alias>', 'NCName')
		  unless $a->{'{}result-prefix'}->{Value} =~ /^$ATT_NCNAME$/
		    or $a->{'{}result-prefix'}->{Value} eq '#default';

		my $pre2 = $a->{'{}result-prefix'}->{Value} eq '#default' 
		  ? '' : $a->{'{}result-prefix'}->{Value};
		my $ns2 = $self->{nsc}->get_uri($pre2);
		#print "COMP: ns-alias> $pre2:$ns2\n";

		$self->doError(221, 3, $a->{'{}result-prefix'}->{Value}, 
			       'stx:namespace-alias') unless $ns2;

		unshift @{$self->{Sheet}->{alias}}, [[$ns1, $pre1], [$ns2, $pre2]];
	    }

	# <stx:group> ----------------------------------------
	} elsif ($el->{LocalName} eq 'group') {

	    if ($self->_allowed($el->{LocalName})) {

		my $g = XML::STX::Group->new($self->{Sheet}->{next_gid},
					     $g_stack_top);
		#print "COMP: >new group $self->{Sheet}->{next_gid} $g\n";

		# the group is linked from the previous group
		$g_stack_top->{groups}->{$self->{Sheet}->{next_gid}} = $g;

		# the group inherits pc2 templates from all ancestors
		foreach (@{$self->{g_stack}}) {
		    push @{$g->{pc2}}, @{$_->{vGroup}};
		    push @{$g->{pc2A}}, @{$_->{vGroupA}};

		    foreach my $p (@{$_->{vGroupP}}) {
			$self->doError(220, 3, $p->{name}, 2)
			  if $g->{pc2P}->{$p->{name}};
			$g->{pc2P}->{$p->{name}} = $p;
		    }
		}

		if (exists $a->{'{}name'}) {
		    $self->doError(214,3,'name','<stx:group>', 'qname') 
		      unless $a->{'{}name'}->{Value} =~ /^$ATT_QNAME$/;
		    $g->{name} = $a->{'{}name'}->{Value};

		    $g->{name} = $self->_expand_qname($g->{name});

		    $self->doError(219, 3, 'group', $g->{name}) 
		      if exists $self->{Sheet}->{named_groups}->{$g->{name}};

		    $self->{Sheet}->{named_groups}->{$g->{name}} = $g;
		}

		# options: recognize-cdata
 		if (exists $a->{'{}recognize-cdata'}) {
 		    if ($a->{'{}recognize-cdata'}->{Value} eq 'no') {
 			$g->{Options}->{'recognize-cdata'} = 0

 		    } elsif ($a->{'{}recognize-cdata'}->{Value} eq 'yes') {
 			$g->{Options}->{'recognize-cdata'} = 1

 		    } elsif ($a->{'{}recognize-cdata'}->{Value} eq 'inherit') {
 			$g->{Options}->{'recognize-cdata'} 
			  = $g->{group}->{Options}->{'recognize-cdata'}

		    } else {
 			$self->doError(205, 3, 'recognize-data', 
 				       $a->{'{}recognize-cdata'}->{Value});
		    }
 		} else {
		    $g->{Options}->{'recognize-cdata'} 
		      = $g->{group}->{Options}->{'recognize-cdata'}
		}

 		# options: pass-through
  		if (exists $a->{'{}pass-through'}) {
  		    if ($a->{'{}pass-through'}->{Value} eq 'all') {
  			$g->{Options}->{'pass-through'} = 1

  		    } elsif ($a->{'{}pass-through'}->{Value} eq 'text') {
  			$g->{Options}->{'pass-through'} = 2

  		    } elsif ($a->{'{}pass-through'}->{Value} eq 'none') {
  			$g->{Options}->{'pass-through'} = 0

  		    } elsif ($a->{'{}pass-through'}->{Value} eq 'inherit') {
  			$g->{Options}->{'pass-through'} 
			  = $g->{group}->{Options}->{'pass-through'}

  		    } else {
  			$self->doError(206, 3, 
  				       $a->{'{}pass-through'}->{Value});
  		    }
  		} else {
		    $g->{Options}->{'pass-through'} 
		      = $g->{group}->{Options}->{'pass-through'}
		}

 		# options: strip-space
  		if (exists $a->{'{}strip-space'}) {
  		    if ($a->{'{}strip-space'}->{Value} eq 'yes') {
  			$g->{Options}->{'strip-space'} = 1

  		    } elsif ($a->{'{}strip-space'}->{Value} eq 'no') {
  			$g->{Options}->{'strip-space'} = 0

  		    } elsif ($a->{'{}strip-space'}->{Value} eq 'inherit') {
  			$g->{Options}->{'strip-space'} 
			  = $g->{group}->{Options}->{'strip-space'}

  		    } else {
  			$self->doError(205, 3, 'strip-space',
  				       $a->{'{}strip-space'}->{Value});
  		    }
  		} else {
		    $g->{Options}->{'strip-space'} 
		      = $g->{group}->{Options}->{'strip-space'}
		}

		push @{$self->{g_stack}}, $g;
		$self->{Sheet}->{next_gid}++;
	    }

	# <stx:template> ----------------------------------------
	} elsif ($el->{LocalName} eq'template') {

	    if ($self->_allowed($el->{LocalName})) {

		my $t = XML::STX::Template->new($self->{Sheet}->{next_tid},
						$g_stack_top);

		# --- match ---
		$self->doError(212, 3, '<stx:template>', 'match')
		  unless exists $a->{'{}match'};

		$t->{pattern} = $a->{'{}match'}->{Value};
		$t->{match} = $self->tokenize_match($a->{'{}match'}->{Value});

		if ($#{$t->{match}->[0]->[0]->{step}} > -1) {
		    foreach (@{$t->{match}}) {
			if ($_->[-1]->{step}->[0] =~ /^@/) {
			    $t->{_att} = 1;
			    $t->{_not_att} = 0;
			} elsif ($_->[-1]->{step}->[0] =~ /^node\(\)/) {
			    $t->{_att} = 1;
			    $t->{_not_att} = 1;
			} else {
			    $t->{_att} = 0;
			    $t->{_not_att} = 1;
			}
		    }
		} else { # '/' root
			$t->{_att} = 0;
			$t->{_not_att} = 1;
		}
		#print "COMP: att: $t->{_att}, not att: $t->{_not_att}\n";

		# --- priority ---
		if (exists $a->{'{}priority'}) {
		    $self->doError(214, 3, 'priority', 
				   '<stx:template>', 'number')
		      unless $a->{'{}priority'}->{Value} 
			=~ /^$ATT_NUMBER$/;
		    $t->{priority} = [$a->{'{}priority'}->{Value}];
		    $t->{eff_p} = $a->{'{}priority'}->{Value};
		}
		unless (exists $t->{priority}) {
		    $t->{priority}
		      = $self->match_priority($a->{'{}match'}->{Value});
			
		    if (defined $t->{priority}->[1]) {
			$t->{eff_p} = 10;
			$g_stack_top->{_complex_priority} = 1;
			
		    } else {
			$t->{eff_p} = $t->{priority}->[0];
		    }
		}

		# --- public ---
		$t->{public} = 0;
		# visible from the current group
		unshift @{$g_stack_top->{pc1}}, $t if $t->{_not_att};
		unshift @{$g_stack_top->{pc1A}}, $t if $t->{_att};

		if (exists $a->{'{}public'}) {

		    if ($a->{'{}public'}->{Value} eq 'yes') {
			$t->{public} = 1;

			if ($t->{_not_att}) {
			    # visible from the parent group
			    unshift @{$self->{g_stack}->[-2]->{pc1}}, $t 
			      if $#{$self->{g_stack}} > 0;
			}
			if ($t->{_att}) { # to match against attributes
			    # visible from the parent group
			    unshift @{$self->{g_stack}->[-2]->{pc1A}}, $t 
			      if $#{$self->{g_stack}} > 0;
			}
			
		    } elsif ($a->{'{}public'}->{Value} ne 'no') {
			$self->doError(205, 3, 'public', $a->{'{}public'}->{Value});
		    }
		} elsif ($e_stack_top->{LocalName} eq 'transform') {
		    $t->{public} = 1;

		    if ($t->{_not_att}) {
			    # visible from the parent group
			unshift @{$self->{g_stack}->[-2]->{pc1}}, $t 
			  if $#{$self->{g_stack}} > 0;
		    }
		    if ($t->{_att}) { # to match against attributes
			# visible from the parent group
			unshift @{$self->{g_stack}->[-2]->{pc1A}}, $t 
			  if $#{$self->{g_stack}} > 0;
		    }
		}

		# --- visibility ---
		$t->{visibility} = 1;
		if (exists $a->{'{}visibility'}) {
			
		    if ($a->{'{}visibility'}->{Value} eq 'group') {
			$t->{visibility} = 2;
			push @{$g_stack_top->{vGroup}}, $t if $t->{_not_att};
			push @{$g_stack_top->{vGroupA}}, $t if $t->{_att};

		    } elsif ($a->{'{}visibility'}->{Value} eq 'global') {
			$t->{visibility} = 3;

			if ($t->{_not_att}) {
			    push @{$g_stack_top->{vGroup}}, $t;
			    unshift @{$self->{Sheet}->{dGroup}->{pc3}}, $t;
			}
			if ($t->{_att}) { # to match against attributes
			    push @{$g_stack_top->{vGroupA}}, $t;
			    unshift @{$self->{Sheet}->{dGroup}->{pc3A}}, $t;
			}
			
		    } elsif ($a->{'{}visibility'}->{Value} ne 'local') {
			$self->doError(204, 3, $a->{'{}visibility'}->{Value});
		    }
		}
	
		# --- new-scope ---
		$t->{'new-scope'} = 0;
		if (exists $a->{'{}new-scope'}) {
		    if ($a->{'{}new-scope'}->{Value} eq 'yes') {
			$t->{'new-scope'} = 1
		    } elsif ($a->{'{}new-scope'}->{Value} ne 'no') {
			$self->doError(205, 3, 'new-scope',
				       $a->{'{}new-scope'}->{Value});
		    }
		}
		
		#print "COMP: >new template $self->{Sheet}->{next_tid} $t\n";
		#print "COMP: >matching $t->{match}\n";
		$g_stack_top->{templates}->{$self->{Sheet}->{next_tid}} = $t;

		$self->{c_template} = $t;
		$self->{Sheet}->{next_tid}++;
	    }

	# <stx:procedure> ----------------------------------------

	} elsif ($el->{LocalName} eq'procedure') {

	    if ($self->_allowed($el->{LocalName})) {

		my $p = XML::STX::Template->new($self->{Sheet}->{next_tid},
						$g_stack_top);

		# --- name ---
		$self->doError(212, 3, '<stx:procedure>', 'name')
		  unless exists $a->{'{}name'};

		$self->doError(214,3,'name','<stx:procedure>', 'qname') 
		  unless $a->{'{}name'}->{Value} =~ /^$ATT_QNAME$/;
		$p->{name} = $a->{'{}name'}->{Value};

		$p->{name} = $self->_expand_qname($p->{name});

		# --- public ---
		$p->{public} = 0;
		# visible from the current group
		$g_stack_top->{pc1P}->{$p->{name}} = $p;

		if (exists $a->{'{}public'}) {

		    if ($a->{'{}public'}->{Value} eq 'yes') {
			$p->{public} = 1;
			#push @{$g_stack_top->{vPublicP}}, $p;

			# visible from the parent group
			$self->{g_stack}->[-2]->{pc1P}->{$p->{name}} = $p 
			  if $#{$self->{g_stack}} > 0;
			
		    } elsif ($a->{'{}public'}->{Value} ne 'no') {
			$self->doError(205, 3, 'public', $a->{'{}public'}->{Value});
		    }

		} elsif ($e_stack_top->{LocalName} eq 'transform') {
		    $p->{public} = 1;
		    #push @{$g_stack_top->{vPublicP}}, $p;

		    # visible from the parent group
		    $self->{g_stack}->[-2]->{pc1P}->{$p->{name}} = $p 
		      if $#{$self->{g_stack}} > 0;
		}

		# --- visibility ---
		$p->{visibility} = 1;
		if (exists $a->{'{}visibility'}) {
			
		    if ($a->{'{}visibility'}->{Value} eq 'group') {
			$p->{visibility} = 2;
			push @{$g_stack_top->{vGroupP}}, $p;

		    } elsif ($a->{'{}visibility'}->{Value} eq 'global') {
			$p->{visibility} = 3;

			push @{$g_stack_top->{vGroupP}}, $p;
			$self->{Sheet}->{dGroup}->{pc3P}->{$p->{name}} = $p;
			
		    } elsif ($a->{'{}visibility'}->{Value} ne 'local') {
			$self->doError(204, 3, $a->{'{}visibility'}->{Value});
		    }
		}

		# --- new-scope ---
		if (exists $a->{'{}new-scope'}) {
		    if ($a->{'{}new-scope'}->{Value} eq 'yes') {
			$p->{'new-scope'} = 1
		    } elsif ($a->{'{}new-scope'}->{Value} ne 'no') {
			$self->doError(205, 3, 'new-scope',
				       $a->{'{}new-scope'}->{Value});
		    }
		}
		
		#print "COMP: >new procedure $self->{Sheet}->{next_tid} $p\n";
		#print "COMP: >name $p->{name}\n";
		$g_stack_top->{procedures}->{$self->{Sheet}->{next_tid}} = $p;

		$self->{c_template} = $p;
		$self->{Sheet}->{next_tid}++;
	    }

	# <stx:process-children> ----------------------------------------
	} elsif ($el->{LocalName} eq'process-children') {

	    if ($self->_allowed($el->{LocalName})) {

		my $group;
		if (exists $a->{'{}group'}) {
		    $self->doError(214,3,'group','<stx:process-children>',
				   'qname') 
		      unless $a->{'{}group'}->{Value} =~ /^$ATT_QNAME$/;
		    $group = $a->{'{}group'}->{Value};

		    $group = $self->_expand_qname($group);
		}

		push @{$self->{c_template}->{instructions}}, 
		  [I_P_CHILDREN_START, $group];
		#print "COMP: >PROCESS_CHILDREN_START\n";
	    }

	# <stx:process-attributes> ----------------------------------------
	} elsif ($el->{LocalName} eq'process-attributes') {

	    if ($self->_allowed($el->{LocalName})) {

		my $group;
		if (exists $a->{'{}group'}) {
		    $self->doError(214,3,'group','<stx:process-attributes>',
				   'qname') 
		      unless $a->{'{}group'}->{Value} =~ /^$ATT_QNAME$/;
		    $group = $a->{'{}group'}->{Value};

		    $group = $self->_expand_qname($group);
		}

		push @{$self->{c_template}->{instructions}}, 
		  [I_P_ATTRIBUTES_START, $group];
		#print "COMP: >PROCESS ATTRIBUTES START\n";
	    }

	# <stx:process-self> ----------------------------------------
	} elsif ($el->{LocalName} eq'process-self') {

	    if ($self->_allowed($el->{LocalName})) {

		my $group;
		if (exists $a->{'{}group'}) {
		    $self->doError(214,3,'group','<stx:process-self>',
				   'qname') 
		      unless $a->{'{}group'}->{Value} =~ /^$ATT_QNAME$/;
		    $group = $a->{'{}group'}->{Value};

		    $group = $self->_expand_qname($group);
		}

		$self->{c_template}->{_self} = 1;

		push @{$self->{c_template}->{instructions}}, 
		  [I_P_SELF_START, $group];
		#print "COMP: >PROCESS SELF START\n";
	    }

	# <stx:call-procedure> ----------------------------------------
	} elsif ($el->{LocalName} eq'call-procedure') {

	    if ($self->_allowed($el->{LocalName})) {

		# --- name ---
		$self->doError(212, 3, '<stx:call-procedure>', 'name')
		  unless exists $a->{'{}name'};
		$self->doError(214,3,'name','<stx:call-procedure>','qname') 
		      unless $a->{'{}name'}->{Value} =~ /^$ATT_QNAME$/;

		my $name = $a->{'{}name'}->{Value};
		$name = $self->_expand_qname($name);

		# --- group ---
		my $group;
		if (exists $a->{'{}group'}) {
		    $self->doError(214,3,'group','<stx:call-procedure>', 'qname') 
		      unless $a->{'{}group'}->{Value} =~ /^$ATT_QNAME$/;
		    $group = $a->{'{}group'}->{Value};

		    $group = $self->_expand_qname($group);
		}

		push @{$self->{c_template}->{instructions}}, 
		  [I_CALL_PROCEDURE_START, $name, $group];
		#print "COMP: >CALL PROCEDURE START\n";
	    }

	# <stx:if> ----------------------------------------
	} elsif ($el->{LocalName} eq 'if') {

	    if ($self->_allowed($el->{LocalName})) {

		$self->doError(212, 3, '<stx:if>', 'test')
		  unless exists $a->{'{}test'};

		my $expr = $self->tokenize($a->{'{}test'}->{Value});
		push @{$self->{c_template}->{instructions}},
		  [I_IF_START, $expr];
		#print "COMP: >IF\n";
	    }

	# <stx:else> ----------------------------------------
	} elsif ($el->{LocalName} eq 'else') {

	    if ($self->_allowed($el->{LocalName})) {

		my $last = $self->{c_template}->{instructions}->[-1]->[0];
		$self->doError(218, 3, 'stx:else', 'stx:if', $last) 
		  if $last != I_IF_END;

		push @{$self->{c_template}->{instructions}}, [I_ELSE_START];
		#print "COMP: >ELSE\n";
	    }

	# <stx:choose> ----------------------------------------
	} elsif ($el->{LocalName} eq 'choose') {

	    if ($self->_allowed($el->{LocalName})) {

		$self->doError(208, 3, 'stx:choose') if $self->{_choose};

		$self->{_choose} = 1;
		#print "COMP: >CHOOSE\n";
	    }

	# <stx:when> ----------------------------------------
	} elsif ($el->{LocalName} eq 'when') {

	    if ($self->_allowed($el->{LocalName})) {

		$self->doError(212, 3, '<stx:when>', 'test')
		  unless exists $a->{'{}test'};

		my $expr = $self->tokenize($a->{'{}test'}->{Value});
		push @{$self->{c_template}->{instructions}},
		  [I_ELSIF_START, $expr];
		#print "COMP: >WHEN\n";
	    }

	# <stx:otherwise> ----------------------------------------
	} elsif ($el->{LocalName} eq 'otherwise') {

	    if ($self->_allowed($el->{LocalName})) {

 		my $last = $self->{c_template}->{instructions}->[-1]->[0];
 		$self->doError(218, 3, 'stx:otherwise', 'stx:when', $last)
		  if $last != I_ELSIF_END;

		push @{$self->{c_template}->{instructions}}, [I_ELSE_START];
		#print "COMP: >OTHERWISE\n";
	    }

	# <stx:value-of> ----------------------------------------
	} elsif ($el->{LocalName} eq 'value-of') {

	    if ($self->_allowed($el->{LocalName})) {

		$self->doError(212, 3, '<stx:value-of>', 'select')
		  unless exists $a->{'{}select'};
		
		$self->doError(213, 3, 'select', '<stx:value-of>')
		  if $a->{'{}select'}->{Value} =~ /\{|\}$/;

		my $expr = $self->tokenize($a->{'{}select'}->{Value});

		my $sep = exists $a->{'{}separator'} 
		  ? $self->_avt($a->{'{}separator'}->{Value}) : ' ';

		push @{$self->{c_template}->{instructions}},
		  [I_CHARACTERS, $expr, $sep];
		#print "COMP: >CHARACTER\n";
	    }

	# <stx:copy> ----------------------------------------
	} elsif ($el->{LocalName} eq 'copy') {

	    if ($self->_allowed($el->{LocalName})) {
		
		my $attributes = '#all';
		if (exists $a->{'{}attributes'}) {
		    $self->doError(217, 3, 'attributes',
				   $a->{'{}attributes'}->{Value}, 
				   'list of qnames')
		      unless $a->{'{}attributes'}->{Value} 
			=~ /^($ATT_QNAMES|#none|#all)$/ 
			  or $a->{'{}attributes'}->{Value} eq '';

		$attributes = $a->{'{}attributes'}->{Value};
		}
		
		push @{$self->{c_template}->{instructions}}, 
		  [I_COPY_START, $attributes];
		#print "COMP: >COPY_START $attributes\n";
	    }

	# <stx:element> or <stx:start-element> -----------------
	} elsif ($el->{LocalName} eq 'element'
		or $el->{LocalName} eq 'start-element') {

	    if ($self->_allowed($el->{LocalName})) {

		$self->doError(212, 3, "<stx:$el->{LocalName}>", 'name')
		  unless exists $el->{Attributes}->{'{}name'};

		my $qn = $self->_avt($a->{'{}name'}->{Value});

		my $ns = exists $a->{'{}namespace'}
		  ? $self->_avt($a->{'{}namespace'}->{Value}) : undef;

		push @{$self->{c_template}->{instructions}},
		  [I_ELEMENT_START, $qn, $ns, clone($self->{nsc})];
		#print "COMP: >ELEMENT_START\n";
	    }

	# <stx:end-element> ----------------------------------------
	} elsif ($el->{LocalName} eq'end-element') {

	    if ($self->_allowed($el->{LocalName})) {

		$self->doError(212, 3, '<stx:end-element>', 'name')
		  unless exists $el->{Attributes}->{'{}name'};

		my $qn = $self->_avt($a->{'{}name'}->{Value});

		my $ns = exists $a->{'{}namespace'}
		  ? $self->_avt($a->{'{}namespace'}->{Value}) : undef; 

		push @{$self->{c_template}->{instructions}},
		  [I_ELEMENT_END, $qn, $ns, clone($self->{nsc})];
		#print "COMP: >ELEMENT_END\n";
	    }

	# <stx:attribute> ----------------------------------------
	} elsif ($el->{LocalName} eq'attribute') {

	    if ($self->_allowed($el->{LocalName})) {
		
		my $ok;
		my $insts = $self->{c_template}->{instructions};
		for (my $i = 0; $i < @$insts; $i++) {

		    last if $insts->[$#$insts - $i]->[0] == I_ATTRIBUTE_END
		      or $insts->[$#$insts - $i]->[0] == I_ELEMENT_START
			or $insts->[$#$insts - $i]->[0] == I_LITERAL_START
			  or $insts->[$#$insts - $i]->[0] == I_COPY_START;
		    # these instructions don't output anything
		    $self->doError(207, 3, $insts->[$#$insts - $i]->[0]) 
		      unless $insts->[$#$insts - $i]->[0] > 100;
		}

		$self->doError(212, 3, "<stx:$el->{LocalName}>", 'name')
		  unless exists $el->{Attributes}->{'{}name'};

		my $qn = $self->_avt($a->{'{}name'}->{Value});

		my $ns = exists $a->{'{}namespace'}
		  ? $self->_avt($a->{'{}namespace'}->{Value}) : undef; 

		my $sel = exists $a->{'{}select'} ? 
		  $self->tokenize($a->{'{}select'}->{Value}) : undef;

		$self->{_attribute_select} = $sel;
		push @{$self->{c_template}->{instructions}},
		  [I_ATTRIBUTE_START, $qn, $ns, clone($self->{nsc}), $sel];
		#print "COMP: >ATTRIBUTE_START\n";
	    }

	# <stx:text> ----------------------------------------
	} elsif ($el->{LocalName} eq 'text') {

	    $self->_allowed($el->{LocalName});

	# <stx:cdata> ----------------------------------------
	} elsif ($el->{LocalName} eq 'cdata') {

	    if ($self->_allowed($el->{LocalName})) {

		push @{$self->{c_template}->{instructions}}, 
		  [I_CDATA_START];
		#print "COMP: >CDATA_START\n";
	    }

	# <stx:comment> ----------------------------------------
	} elsif ($el->{LocalName} eq'comment') {

	    if ($self->_allowed($el->{LocalName})) {
		
		push @{$self->{c_template}->{instructions}}, 
		  [I_COMMENT_START];
		#print "COMP: >COMMENT_START\n";
	    }

	# <stx:processing-instruction> -----------------------------------
	} elsif ($el->{LocalName} eq'processing-instruction') {

	    if ($self->_allowed($el->{LocalName})) {

		$self->doError(212, 3, "<stx:$el->{LocalName}>", 'name')
		  unless exists $el->{Attributes}->{'{}name'};

		my $target = $self->_avt($el->{Attributes}->{'{}name'}->{Value});

		push @{$self->{c_template}->{instructions}}, 
		  [I_PI_START, $target];
		#print "COMP: >PI_START\n";
	    }

	# <stx:variable> ----------------------------------------
	} elsif ($el->{LocalName} eq 'variable') {

	    if ($self->_allowed($el->{LocalName})) {
		
		$self->doError(212, 3, "<stx:$el->{LocalName}>", 'name')
		  unless exists $el->{Attributes}->{'{}name'};
		$self->doError(217, 3, 'name', 
			       $a->{'{}name'}->{Value}, 'qname')
		  unless $a->{'{}name'}->{Value} =~ /^($ATT_QNAME)$/;

		my $name = $a->{'{}name'}->{Value};
		$name = $self->_expand_qname($name);

		my $select;
		my $default_select;
		if (exists $a->{'{}select'}) {
		    $self->doError(213, 3, 'select', '<stx:variable>')
		      if $a->{'{}select'}->{Value} =~ /^\{|\}$/;
		    $select = $self->tokenize($a->{'{}select'}->{Value});
		    $default_select = 0;
		} else {
		    $select = ['""']; # the empty string
		    $default_select = 1;
		}

		$self->{_variable_select} = $select;

		# local variable ------------------------------
		if ($self->{c_template}) {

		    # variable already declared
		    $self->doError(211, 3, 'Local variable', "\'$name\'") 
		      if exists $self->{c_template}->{vars}->[0]->{$name};

		    push @{$e_stack_top->{vars}}, $name;
		    $self->{c_template}->{vars}->[0]->{$name} = [];

		    push @{$self->{c_template}->{instructions}}, 
		      [I_VARIABLE_START, $name, $select, $default_select];
		    #print "COMP: >VARIABLE_START\n";

		# group variable ------------------------------
		} else {

		    # variable already declared
		    $self->doError(211, 3, 'Group variable', "\'$name\'") 
		      if $g_stack_top->{vars}->[0]->{$name};

		    my $keep_value = 0; 
 		    if (exists $a->{'{}keep-value'}) {
 			if ($a->{'{}keep-value'}->{Value} eq 'yes') {
 			    $keep_value = 1
 			} elsif ($a->{'{}keep-value'}->{Value} ne 'no') {
 			    $self->doError(205, 3, 'keep-value',
 					   $a->{'{}keep-value'}->{Value});
 			}
 		    }

		    # actual value
		    $g_stack_top->{vars}->[0]->{$name}->[0]
		      = $self->_static_eval($select);
		    # init value
		    $g_stack_top->{vars}->[0]->{$name}->[1]
		      = clone($g_stack_top->{vars}->[0]->{$name}->[0]);
		    # keep value
		    $g_stack_top->{vars}->[0]->{$name}->[2]
		      = $keep_value;
 		    #print "COMP: >GROUP_VARIABLE\n";
		}
	    }

	# <stx:param> ----------------------------------------
	} elsif ($el->{LocalName} eq 'param') {

	    if ($self->_allowed($el->{LocalName})) {
		
		$self->doError(212, 3, "<stx:$el->{LocalName}>", 'name')
		  unless exists $el->{Attributes}->{'{}name'};
		$self->doError(217, 3, 'name', 
			       $a->{'{}name'}->{Value}, 'qname')
		  unless $a->{'{}name'}->{Value} =~ /^($ATT_QNAME)$/;

		my $name = $a->{'{}name'}->{Value};
		$name = $self->_expand_qname($name);

		my $select;
		my $default_select;
		if (exists $a->{'{}select'}) {
		    $self->doError(213, 3, 'select', '<stx:param>')
		      if $a->{'{}select'}->{Value} =~ /^\{|\}$/;
		    $select = $self->tokenize($a->{'{}select'}->{Value});
		    $default_select = 0;
		} else {
		    $select = ['""']; # the empty string
		    $default_select = 1;
		}

		my $req = 0; 
		if (exists $a->{'{}required'}) {
		    if ($a->{'{}required'}->{Value} eq 'yes') {
			$req = 1
		    } elsif ($a->{'{}required'}->{Value} ne 'no') {
			$self->doError(205, 3, 'required',
				       $a->{'{}required'}->{Value});
		    }
		}

		$self->{_variable_select} = $select;

		# local parameter ------------------------------
		if ($self->{c_template}) {

		    # parameter already declared
		    $self->doError(211, 3, 'Local parameter', "\'$name\'") 
		      if exists $self->{c_template}->{vars}->[0]->{$name};

		    push @{$e_stack_top->{vars}}, $name;
		    $self->{c_template}->{vars}->[0]->{$name} = [];

		    push @{$self->{c_template}->{instructions}}, 
		      [I_PARAMETER_START, $name, $select, $default_select, $req];
		    #print "COMP: >PARAMETER_START\n";

		# stylesheet parameter ------------------------------
		} else {

		    # parameter already declared
		    $self->doError(211, 3, 'Stylesheet parameter', "\'$name\'") 
		      if $self->{Sheet}->{dGroup}->{vars}->[0]->{$name};

		    # actual value
		    $self->{Sheet}->{dGroup}->{vars}->[0]->{$name}->[0]
		      = $self->_static_eval($select);
		    # init value
		    $self->{Sheet}->{dGroup}->{vars}->[0]->{$name}->[1]
		      = clone($self->{Sheet}->{dGroup}->{vars}->[0]->{$name}->[0]);
		    # keep value
		    $self->{Sheet}->{dGroup}->{vars}->[0]->{$name}->[2] = 0;

		    # list of params
		    $self->{Sheet}->{dGroup}->{pars}->{$name} = $req;
 		    #print "COMP: >GROUP_VARIABLE - parameter\n";
		}

	    }

	# <stx:with-param> ----------------------------------------
	} elsif ($el->{LocalName} eq 'with-param') {

	    if ($self->_allowed($el->{LocalName})) {
		
		$self->doError(212, 3, "<stx:$el->{LocalName}>", 'name')
		  unless exists $el->{Attributes}->{'{}name'};
		$self->doError(217, 3, 'name', 
			       $a->{'{}name'}->{Value}, 'qname')
		  unless $a->{'{}name'}->{Value} =~ /^($ATT_QNAME)$/;

		my $name = $a->{'{}name'}->{Value};
		$name = $self->_expand_qname($name);

		my $select;
		my $default_select;
		if (exists $a->{'{}select'}) {
		    $self->doError(213, 3, 'select', '<stx:with-param>')
		      if $a->{'{}select'}->{Value} =~ /^\{|\}$/;
		    $select = $self->tokenize($a->{'{}select'}->{Value});
		    $default_select = 0;
		} else {
		    $select = ['""']; # the empty string
		    $default_select = 1;
		}
		
		push @{$self->{c_template}->{instructions}}, 
		  [I_WITH_PARAM_START, $name, $select, $default_select];
		#print "COMP: >WITH_PARAM\n";
	    }

	# <stx:assign> ----------------------------------------
	} elsif ($el->{LocalName} eq 'assign') {

	    if ($self->_allowed($el->{LocalName})) {
		
		$self->doError(212, 3, "<stx:$el->{LocalName}>", 'name')
		  unless exists $el->{Attributes}->{'{}name'};
		$self->doError(217, 3, 'name', 
			       $a->{'{}name'}->{Value}, 'qname')
		  unless $a->{'{}name'}->{Value} =~ /^($ATT_QNAME)$/;

		my $name = $a->{'{}name'}->{Value};
		$name = $self->_expand_qname($name);

		my $select;
		if (exists $a->{'{}select'}) {
		    $self->doError(213, 3, 'select', '<stx:assign>')
		      if $a->{'{}select'}->{Value} =~ /\{|\}$/;
		    $select = $self->tokenize($a->{'{}select'}->{Value});
		}

		$self->{_variable_select} = $select;

		push @{$self->{c_template}->{instructions}},
		  [I_ASSIGN_START, $name, $select];
		#print "COMP: >ASSIGN_START\n";
	    }


	# <stx:buffer> ----------------------------------------
	} elsif ($el->{LocalName} eq 'buffer') {

	    if ($self->_allowed($el->{LocalName})) {
		
		# --- name ---
		$self->doError(212, 3, "<stx:$el->{LocalName}>", 'name')
		  unless exists $el->{Attributes}->{'{}name'};
		$self->doError(217, 3, 'name', 
			       $a->{'{}name'}->{Value}, 'qname')
		  unless $a->{'{}name'}->{Value} =~ /^($ATT_QNAME)$/;

		my $name = $a->{'{}name'}->{Value};
		$name = $self->_expand_qname($name);

		# local buffer ------------------------------
		if ($self->{c_template}) {

		    # buffer already declared
		    $self->doError(211, 3, 'Local buffer', "\'$name\'") 
		      if exists $self->{c_template}->{bufs}->[0]->{$name};

		    push @{$e_stack_top->{bufs}}, $name;

		    push @{$self->{c_template}->{instructions}}, 
		      [I_BUFFER_START, $name];
		    #print "COMP: >BUFFER_START\n";

		# group buffer ------------------------------
		} else {

		    # buffer already declared
		    $self->doError(211, 3, 'Group buffer', "\'$name\'") 
		      if $self->{c_group}->{bufs}->[0]->{$name};

		    # new buffer
		    my $b = XML::STX::Buffer->new($name);
		    $g_stack_top->{bufs}->[0]->{$name} = $b;

 		    #print "COMP: >GROUP_BUFFER\n";
		}
	    }

	# <stx:result-buffer> ----------------------------------------
	} elsif ($el->{LocalName} eq 'result-buffer') {

	    if ($self->_allowed($el->{LocalName})) {
		
		# --- name ---
		$self->doError(212, 3, "<stx:$el->{LocalName}>", 'name')
		  unless exists $el->{Attributes}->{'{}name'};
		$self->doError(217, 3, 'name', 
			       $a->{'{}name'}->{Value}, 'qname')
		  unless $a->{'{}name'}->{Value} =~ /^($ATT_QNAME)$/;

		my $name = $a->{'{}name'}->{Value};
		$name = $self->_expand_qname($name);

		my $clear = 0;
		if (exists $a->{'{}clear'}) {
		    if ($a->{'{}clear'}->{Value} eq 'yes') {
			$clear = 1;
		    } elsif ($a->{'{}clear'}->{Value} ne 'no') {
			$self->doError(205, 3, 'clear', $a->{'{}clear'}->{Value});
		    }
		}

		push @{$self->{c_template}->{instructions}},
		  [I_RES_BUFFER_START, $name, $clear];
		#print "COMP: >RESULT_BUFFER_START\n";
	    }

	# <stx:process-buffer> ----------------------------------------
	} elsif ($el->{LocalName} eq'process-buffer') {

	    if ($self->_allowed($el->{LocalName})) {

		# --- name ---
		$self->doError(212, 3, "<stx:$el->{LocalName}>", 'name')
		  unless exists $el->{Attributes}->{'{}name'};
		$self->doError(217, 3, 'name', 
			       $a->{'{}name'}->{Value}, 'qname')
		  unless $a->{'{}name'}->{Value} =~ /^($ATT_QNAME)$/;

		my $name = $a->{'{}name'}->{Value};
		$name = $self->_expand_qname($name);

		# --- group ---
		my $group;
		if (exists $a->{'{}group'}) {
		    $self->doError(214,3,'group','<stx:process-buffer>',
				   'qname') 
		      unless $a->{'{}group'}->{Value} =~ /^$ATT_QNAME$/;

		    $group = $self->_expand_qname($a->{'{}group'}->{Value});
		}

		push @{$self->{c_template}->{instructions}}, 
		  [I_P_BUFFER_START, $name, $group];
		#print "COMP: >PROCESS BUFFER START\n";
	    }

	# <stx:result-document> ----------------------------------------
	} elsif ($el->{LocalName} eq 'result-document') {

	    if ($self->_allowed($el->{LocalName})) {
		
		# --- href ---
		$self->doError(212, 3, '<stx:result-document>', 'href')
		  unless exists $a->{'{}href'};

		my $href = $self->tokenize($a->{'{}href'}->{Value});

		# --- encoding ---
		my $encoding;
		if (exists $a->{'{}encoding'}) {
		    $self->doError(214,3,'encoding','<stx:result-document>',
				   'string') 
		      unless $a->{'{}group'}->{Value} =~ /^$ATT_STRING$/;

		    $encoding = $a->{'{}encoding'}->{Value};
		}
		
		push @{$self->{c_template}->{instructions}},
		  [I_RES_DOC_START, $href, $encoding];
		#print "COMP: >RESULT_DOCUMENT_START\n";
	    }

	# <stx:process-document> ----------------------------------------
	} elsif ($el->{LocalName} eq'process-document') {

	    if ($self->_allowed($el->{LocalName})) {

		# --- href ---
		$self->doError(212, 3, '<stx:process-document>', 'href')
		  unless exists $a->{'{}href'};

		my $href = $self->tokenize($a->{'{}href'}->{Value});

		# --- group ---
		my $group;
		if (exists $a->{'{}group'}) {
		    $self->doError(214,3,'group','<stx:process-document>',
				   'qname') 
		      unless $a->{'{}group'}->{Value} =~ /^$ATT_QNAME$/;

		    $group = $self->_expand_qname($a->{'{}group'}->{Value});
		}

		# --- base ---
		my $base = exists $a->{'{}base'} 
		  ? $self->_avt($a->{'{}base'}->{Value}) : undef;
		
		push @{$self->{c_template}->{instructions}}, 
		  [I_P_DOC_START, $href, $group, $base];
		#print "COMP: >PROCESS DOCUMENT START\n";
	    }

	} else {
	    $self->doError(209, 3, "<stx:$el->{LocalName}>")
	}

    # literals ==================================================
    } else {

	if ($self->_allowed('_literal')) {

	    if (exists $el->{Attributes}) {
		foreach my $ns (keys %{$el->{Attributes}}) {

		    # tokenize AVT in attributes
		    $el->{Attributes}->{$ns}->{Value} 
		      = $self->_avt($el->{Attributes}->{$ns}->{Value});
		}
	    }
		
	    my $i = [I_LITERAL_START, $el];
	    push @{$self->{c_template}->{instructions}}, $i;
	    #print "COMP: >LITERAL_START $el->{Name}\n";

	} else { #???
	    $self->doError(210, 3, $el->{Name}) 
	      unless $el->{NamespaceURI};
	}
    }

    push @{$self->{e_stack}}, $el;
}

sub end_element {
    my $self = shift;
    my $el = shift;

    #print "COMP: \/$el->{Name}\n";

    # STX instructions ==================================================
    if (defined $el->{NamespaceURI} and $el->{NamespaceURI} eq STX_NS_URI) {

	# <stx:transform> ----------------------------------------
	if ($el->{LocalName} eq 'transform') {

	    if ($self->{include}) {
		#$self->_dump_g_stack;
		my $g = pop @{$self->{g_stack}};
		$self->_sort_templates($g->{pc1});
		$self->_sort_templates($g->{pc1A});
		$self->_sort_templates($g->{pc2});
		$self->_sort_templates($g->{pc2A});

	    } else {
		# nothing else is allowed
		$self->_sort_templates($self->{Sheet}->{dGroup}->{pc1});
		$self->_sort_templates($self->{Sheet}->{dGroup}->{pc1A});
		$self->_sort_templates($self->{Sheet}->{dGroup}->{pc2});
		$self->_sort_templates($self->{Sheet}->{dGroup}->{pc2A});
		$self->_sort_templates($self->{Sheet}->{dGroup}->{pc3});
		$self->_sort_templates($self->{Sheet}->{dGroup}->{pc3A});
		$self->{end} = 1;
	    }

	# <stx:process-children> ----------------------------------------
	} elsif ($el->{LocalName} eq 'process-children') {

	    push @{$self->{c_template}->{instructions}}, [I_P_CHILDREN_END];
	    #print "COMP: >PROCESS CHILDREN END /$el->{Name}\n";

	# <stx:process-self> ----------------------------------------
	} elsif ($el->{LocalName} eq 'process-self') {

	    push @{$self->{c_template}->{instructions}}, [I_P_SELF_END];
	    #print "COMP: >PROCESS SELF END /$el->{Name}\n";

	# <stx:process-attributes> ----------------------------------------
	} elsif ($el->{LocalName} eq 'process-attributes') {

	    push @{$self->{c_template}->{instructions}}, [I_P_ATTRIBUTES_END];
	    #print "COMP: >PROCESS ATTRIBUTES END /$el->{Name}\n";

	# <stx:process-buffer> ----------------------------------------
	} elsif ($el->{LocalName} eq 'process-buffer') {

	    push @{$self->{c_template}->{instructions}}, [I_P_BUFFER_END];
	    #print "COMP: >PROCESS BUFFER END /$el->{Name}\n";

	# <stx:process-document> ----------------------------------------
	} elsif ($el->{LocalName} eq 'process-document') {

	    push @{$self->{c_template}->{instructions}}, [I_P_DOC_END];
	    #print "COMP: >PROCESS DOCUMENT END /$el->{Name}\n";

	# <stx:call-procedure> ----------------------------------------
	} elsif ($el->{LocalName} eq 'call-procedure') {

	    push @{$self->{c_template}->{instructions}}, [I_CALL_PROCEDURE_END];
	    #print "COMP: >CALL PROCEDURE END /$el->{Name}\n";

	# <stx:variable> ----------------------------------------
	} elsif ($el->{LocalName} =~ /^(variable|param)$/) {

	    # local variable
	    if ($self->{c_template}) {
		
		push @{$self->{c_template}->{instructions}}, [I_VARIABLE_END];
		#print "COMP: >VARIABLE END\n";
	    } else {
		# tbd
	    }
	    
	# <stx:assign> ----------------------------------------
	} elsif ($el->{LocalName} eq 'assign') {

	    push @{$self->{c_template}->{instructions}}, [I_ASSIGN_END];
	    #print "COMP: >ASSIGN_END\n";

	# <stx:with-param> ----------------------------------------
	} elsif ($el->{LocalName} eq 'with-param') {

	    push @{$self->{c_template}->{instructions}}, [I_WITH_PARAM_END];
	    #print "COMP: >WITH_PARAM_END\n";

	# <stx:group> ----------------------------------------
	} elsif ($el->{LocalName} eq 'group') {
	    #$self->_dump_g_stack;
	    my $g = pop @{$self->{g_stack}};
	    $self->_sort_templates($g->{pc1});
	    $self->_sort_templates($g->{pc1A});
	    $self->_sort_templates($g->{pc2});
	    $self->_sort_templates($g->{pc2A});

	# <stx:template> ----------------------------------------
	} elsif ($el->{LocalName} eq 'template') {
	    $self->{c_template} = undef;

	# <stx:procedure> ----------------------------------------
	} elsif ($el->{LocalName} eq 'procedure') {
	    $self->{c_template} = undef;

	# <stx:copy> ----------------------------------------
	} elsif ($el->{LocalName} eq 'copy') {

	    push @{$self->{c_template}->{instructions}}, [I_COPY_END];
	    #print "COMP: >COPY_END\n";

	# <stx:element> ----------------------------------------
	} elsif ($el->{LocalName} eq 'element') {

	    push @{$self->{c_template}->{instructions}}, [I_ELEMENT_END];
	    #print "COMP: >ELEMENT_END /$el->{Name}\n";

	# <stx:attribute> ----------------------------------------
	} elsif ($el->{LocalName} eq 'attribute') {

	    push @{$self->{c_template}->{instructions}}, [I_ATTRIBUTE_END];
	    #print "COMP: >ATTRIBUTE_END\n";

        # <stx:cdata> ----------------------------------------
	} elsif ($el->{LocalName} eq 'cdata') {

	    push @{$self->{c_template}->{instructions}}, [I_CDATA_END];
	    #print "COMP: >CDATA_END\n";

        # <stx:comment> ----------------------------------------
	} elsif ($el->{LocalName} eq 'comment') {

	    push @{$self->{c_template}->{instructions}}, [I_COMMENT_END];
	    #print "COMP: >COMMENT_END\n";

        # <stx:processing-instruction> -----------------------------------
	} elsif ($el->{LocalName} eq 'processing-instruction') {

	    push @{$self->{c_template}->{instructions}}, [I_PI_END];
	    #print "COMP: >PI_END\n";

	# <stx:if> ----------------------------------------
	} elsif ($el->{LocalName} eq 'if') {

	    push @{$self->{c_template}->{instructions}}, [I_IF_END];
	    #print "COMP: >IF_END\n";

	# <stx:else> ----------------------------------------
	} elsif ($el->{LocalName} eq 'else') {

	    push @{$self->{c_template}->{instructions}}, [I_ELSE_END];
	    #print "COMP: >ELSE_END\n";

	# <stx:choose> ----------------------------------------
	} elsif ($el->{LocalName} eq 'choose') {

	    $self->{_choose} = undef;
	    #print "COMP: >CHOOSE_END\n";

	# <stx:when> ----------------------------------------
	} elsif ($el->{LocalName} eq 'when') {

	    push @{$self->{c_template}->{instructions}}, [I_ELSIF_END];
	    #print "COMP: >WHEN_END\n";

	# <stx:otherwise> ----------------------------------------
	} elsif ($el->{LocalName} eq 'otherwise') {

	    push @{$self->{c_template}->{instructions}}, [I_ELSE_END];
	    #print "COMP: >OTHERWISE_END\n";

	# <stx:buffer> ----------------------------------------
	} elsif ($el->{LocalName} eq 'buffer') {

	    # local buffer
	    if ($self->{c_template}) {
		push @{$self->{c_template}->{instructions}}, [I_BUFFER_END];
		#print "COMP: >BUFFER_END\n";

	    } else {
		# kontrola pres lookahead
	    }

	# <stx:result-buffer> ----------------------------------------
	} elsif ($el->{LocalName} eq 'result-buffer') {

	    push @{$self->{c_template}->{instructions}}, [I_RES_BUFFER_END];
	    #print "COMP: >RESULT_BUFFER_END\n";

	# <stx:result-document> ----------------------------------------
	} elsif ($el->{LocalName} eq 'result-document') {

	    push @{$self->{c_template}->{instructions}}, [I_RES_DOC_END];
	    #print "COMP: >RESULT_DOCUMENT_END\n";
	}

	# end tags for empty elements can be ignored, their emptiness is 
	# checked elsewhere

    # literals
    } else {
	
	push @{$self->{c_template}->{instructions}}, [I_LITERAL_END, $el];
	#print "COMP: >LITERAL_END /$el->{Name}\n";
    }

    my $e = pop @{$self->{e_stack}};

    # end of local variable visibility
    foreach (@{$e->{vars}}) {
	push @{$self->{c_template}->{instructions}}, [I_VARIABLE_SCOPE_END, $_];
	#$self->{c_template}->{vars}->[0]->{$_} = undef;
	#print "COMP: >VARIABLE_SCOPE_END $_\n";
    }
    # end of local buffer visibility
    foreach (@{$e->{bufs}}) {
	push @{$self->{c_template}->{instructions}}, [I_BUFFER_SCOPE_END, $_];
	#$self->{c_template}->{bufs}->[0]->{$_} = undef;
	#print "COMP: >BUFFER_SCOPE_END $_\n";
    }

    $self->{nsc}->popContext;
}

sub characters {
    my $self = shift;
    my $char = shift;

    # whitespace only
    if ($char->{Data} =~ /^\s*$/) {
	my $parent = $self->{e_stack}->[-1];
	if ($parent->{NamespaceURI} eq STX_NS_URI
	   and $parent->{LocalName} =~ /^(text|cdata)$/) {

	    if ($self->_allowed('_text')) {
		push @{$self->{c_template}->{instructions}},
		  [I_CHARACTERS, $char->{Data}];
		#print "COMP: >CHARACTERS - $char->{Data}\n";
	    }
	}

    # not whitespace only
    } else {
	if ($self->_allowed('_text')) {
	    push @{$self->{c_template}->{instructions}},
	      [I_CHARACTERS, $char->{Data}];
	    #print "COMP: >CHARACTERS - $char->{Data}\n";
	}	
    }
}

sub processing_instruction {
    my $self = shift;
    my $pi = shift;
}

sub ignorable_whitespace {
}

sub start_prefix_mapping {
    my ($self, $ns) = @_;

    $self->{nsc}->declare_prefix($ns->{Prefix}, $ns->{NamespaceURI});
}

sub end_prefix_mapping {
    my ($self, $ns) = @_;

    $self->{nsc}->undeclare_prefix($ns->{Prefix});
}

sub skipped_entity {
}

# lexical ----------------------------------------

sub start_cdata {
    my $self = shift;
}

sub end_cdata {
    my $self = shift;
}

sub comment {
}

sub start_dtd {
}

sub end_dtd {
}

sub start_entity {
}

sub end_entity {
}

# error ----------------------------------------

sub warning {
}

sub error {
}

sub fatal_error {
}

# static evaluation ----------------------------------------

sub _static_eval {
    my ($self, $val) = @_;

    my $spath = XML::STX::STXPath->new();
    my $seq = $spath->expr(undef, $val);

    return $seq;
}

# tokenize ----------------------------------------

sub tokenize_match {
    my ($self, $pattern) = @_;
    my $tokens = [];

    foreach my $path (split('\|',$pattern)) {

	my $steps = [];

	$path =~ s/^\/\///g;
	$path =~ s/^\//&R/g;
	$path =~ s/\/\//&&&A/g;
	$path =~ s/\//&&&P/g;
	$path = '&N' . $path unless substr($path,0,2) eq '&R';

	foreach (split('&&', $path)) {
	    my $left = substr($_,1,1);
	    my $step = $self->tokenize(substr($_,2));
	    push @$steps, { left => $left, step => $step};
	}
	push @$tokens, $steps;
    }
    return $tokens;
}

sub match_priority {
    my ($self, $pattern) = @_;
    my $priority = [];

    foreach my $path (split('\|',$pattern)) {

	my @steps = split('/|//',$path);
	my $last = $steps[-1];
	my $p = 0.5;

	if ($#steps == 0) {

	    if ($last =~ /^$QName$/) {
		$p = 0;
		
	    } elsif ($last =~ /^processing-instruction\(?:$LITERAL\)$/) {
		$p = 0;

	    } elsif ($last =~ /^cdata\(\)$/) {
		$p = 0;
		
	    } elsif ($last =~ /^(?:$NCWild)$/) {
		$p = -0.25;
		
	    } elsif ($last =~ /^(?:$QNWild)$/) {
		$p = -0.25;
		
	    } elsif ($last =~ /^$NODE_TYPE$/) {
		$p = -0.5;
	    }
	}
	#print "TOK: last step: $last, more steps: $#steps, priority: $p\n";
	push @$priority, $p;
    }
    return $priority;
}

sub tokenize {
    my ($self, $path) = @_;
    study $path;

    my @tokens = ();
    #print "TOK: tokenizing: $path\n";
    
    while($path =~ m/\G
        \s*                                   # ignore all whitespace
        (   $LITERAL|                         # literal
            $DOUBLE_RE|                       # double numbers
            $NUMBER_RE|                       # digits
            \.\.|                             # parent
            \.|                               # current node
            $NODE_TYPE|                       # node type
            processing-instruction|           # pi, to allow pi(target)  
            \$$QName|                         # variable reference
            $QName\(|                         # function
            $NCWild|$QName|$QNWild|           # QName
            \@($NCWild|$QName|$QNWild)|       # attribute
            \!=|<=|\-|>=|\/\/|and|or|mod|div| # multi-char seps
            [,\+=\|<>\/\(\[\]\)]|             # single char seps
            (?<!(\@|\(|\[))\*|                # multiply operator rules
            $                                 # end of query
        )
        \s* # ignore all whitespace
        /gcxso) {

        my ($token) = ($1);

        if (length($token)) {
            #print "TOK: token: $token\n";

	    # resolving QNames ####################
	    if ($token =~ /^$QName\($/o) {
		$token = $self->_expand_prefixedFce($token);

		$token = substr($token, 0, length($token) - 1);
		push @tokens, $token, '(';

	    } elsif ($token =~ /^$NCName$/o 
		     && $token !~ /^(?:and|or|mod|div)$/) {

 		if ($self->{Sheet}->{Options}->
		    {'stxpath-default-namespace'}->[-1]) {
 		    $token = '{' . $self->{Sheet}->{Options}->
		      {'stxpath-default-namespace'}->[-1]
 			. '}' . $token;
		}
		push @tokens, $token;

	    } elsif ($token =~ /^([\@\$])?($QName)$/o) {
		$token = $1 . $self->_expand_prefixedQN($2);
		push @tokens, $token;

	    } elsif ($token =~ /^(\@)?($NCName):\*$/o) {
		$token = $1 . $self->_expand_prefixedQN("$2:lname");
		$token =~ s/lname$/*/;
		push @tokens, $token;

	    } elsif ($token =~ /^(\@)?\*:($NCName|\*)$/o) {
		$token = $1 . "{*}$2";
		push @tokens, $token;

	    } else {
		push @tokens, $token;
	    }
            #print "TOK: exp. token: $token\n";
        }
    }

    if (pos($path) < length($path)) {
        my $marker = ("." x (pos($path)-1));
        $path = substr($path, 0, pos($path) + 8) . "...";
        $path =~ s/\n/ /g;
        $path =~ s/\t/ /g;
	$self->doError(1, 3, $path, $marker);
    }

    return \@tokens;
}

# structure ----------------------------------------

my $s_group = ['variable','buffer','template','procedure','include','group'];

my $s_top_level = [@$s_group, 'param', 'namespace-alias'];

my $s_text_constr = ['text','cdata','value-of','if','else','choose','_text'];

my $s_content_constr = [@$s_text_constr ,'call-procedure', 'copy',
			'process-attributes', 'process-self', 'element',
			'start-element', 'end-element', 'comment',
			'processing-instruction', 'variable', 'param', 
			'assign', 'buffer', 'result-buffer', 'process-buffer',
			'result-document', 'process-document', 'for-each',
			'_literal', 'attribute'];

my $s_template = [@$s_content_constr, 'process-children'];

my $sch = {
	   transform => $s_top_level,
	   group => $s_group,
	   template => $s_template,
	   procedure => $s_template,
	   'process-children' => ['with-param'],
	   'process-attributes' => ['with-param'],
	   'process-self' => ['with-param'],
	   'process-siblings' => ['with-param'],
	   'process-document' => ['with-param'],
	   'process-buffer' => ['with-param'],
	   'call-procedure' => ['with-param'],
	   'with-param' => $s_text_constr,
	   param => $s_text_constr,
	   copy => $s_template,
	   element => $s_template,
	   attribute => $s_text_constr,
	   'processing-instruction' => $s_text_constr,
	   comment => $s_text_constr,
	   'if' => $s_template,
	   'else' => $s_template,
	   choose => ['when','otherwise'],
	   when => $s_template,
	   otherwise => $s_template,
	   'for-each' => $s_template,
	   variable => $s_text_constr,
	   assign => $s_text_constr,
	   text => ['_text'],
	   cdata => ['_text'],
	   buffer => $s_template,
	   'result-buffer' => $s_template,
	   'result-document' => $s_template,
	   _literal => $s_template,
	  };

sub _allowed {
    my ($self, $lname) = @_;

    if ($#{$self->{e_stack}} == -1) {

	$self->doError(202, 3, $lname) 
	  unless $lname eq 'transform';

    } else {
	my $parent = $self->{e_stack}->[-1];

	my $s_key = (defined $parent->{NamespaceURI} 
	  and $parent->{NamespaceURI} eq STX_NS_URI)
	  ? $parent->{LocalName} : '_literal';

	$self->doError(215, 3, $lname, $parent->{Name})
	  unless grep($_ eq $lname ,@{$sch->{$s_key}});
    }
    return 1;
}

# utils ----------------------------------------

sub _avt {
    my ($self, $val) = @_;

    if ($val =~ /^\{([^\}\{]*)\}$/) {
	return $self->tokenize($1);

    } elsif ($val =~ /^.*\{([^\}\{]*)\}.*$/) {
	$val =~ s/^(.*)$/concat('$1')/;
	$val =~ s/\{/',/g;
	$val =~ s/\}/,'/g;
	$val =~ s/'',|,''//g;
	return $self->tokenize($val);

    } else {
	return $val;	
    }
}

sub _sort_templates {
    my ($self, $t) = @_;
    my $sorted = 1;

    while ($sorted) {
	$sorted = 0;
	for (my $i=0; $i < $#$t; $i++) {
	    if ($t->[$i+1]->{eff_p} > $t->[$i]->{eff_p}) {
		my $tmp = $t->[$i];
		$t->[$i] = $t->[$i+1];
		$t->[$i+1] = $tmp;
		$sorted = 1;
	    }
	}
    }
}

sub _expand_qname {
    my ($self, $qname) = @_;

    my @n = $self->{nsc}->process_element_name($qname);
    return $n[0] ? "{$n[0]}$n[2]" : $qname;
}

# default NS is ignored
sub _expand_prefixedQN {
    my ($self, $qname) = @_;

    my @n = $self->{nsc}->process_attribute_name($qname);
    return $n[0] ? "{$n[0]}$n[2]" : $qname;
}

# default function NS is used
sub _expand_prefixedFce {
    my ($self, $qname) = @_;

    my @n = $self->{nsc}->process_attribute_name($qname);
    return $n[0] ? "{$n[0]}$n[2]" : '{' . STX_FNS_URI . "}$n[2]";
}

sub _process_templates {
    my ($self, $g) = @_;

    foreach my $t (keys %{$g->{templates}}) {

	# namespace-alias
	foreach my $i (@{$g->{templates}->{$t}->{instructions}}) {
	    if ($i->[0] == I_LITERAL_START or $i->[0] == I_LITERAL_END) {

		foreach (@{$self->{Sheet}->{alias}}) {
		    if ($i->[1]->{NamespaceURI} eq $_->[0]->[0]) {
			$i->[1]->{NamespaceURI} = $_->[1]->[0];
			$i->[1]->{Prefix} = $_->[1]->[1];
			$i->[1]->{Name} = $i->[1]->{Prefix} 
			  ? "$i->[1]->{Prefix}:$i->[1]->{LocalName}" 
			    : $i->[1]->{LocalName};
			last;
		    }
		}

		if (exists $i->[1]->{Attributes}) {
		    foreach my $ns (keys %{$i->[1]->{Attributes}}) {

			foreach (@{$self->{Sheet}->{alias}}) {
			    if ($i->[1]->{Attributes}->{$ns}->{NamespaceURI} 
				eq $_->[0]->[0]) {
				my $key = "{$_->[1]->[0]}" 
				  . $i->[1]->{Attributes}->{$ns}->{LocalName};

				$i->[1]->{Attributes}->{$key} 
				  = $i->[1]->{Attributes}->{$ns};
				delete $i->[1]->{Attributes}->{$ns};

				$i->[1]->{Attributes}->{$key}->{NamespaceURI} 
				  = $_->[1]->[0];
				$i->[1]->{Attributes}->{$key}->{Prefix} 
				  = $_->[1]->[1];
				$i->[1]->{Attributes}->{$key}->{Name} 
				  = $i->[1]->{Attributes}->{$key}->{Prefix} 
				    ? "$i->[1]->{Attributes}->{$key}->{Prefix}:"
				      . $i->[1]->{Attributes}->{$key}->{LocalName}
					: $i->[1]->{Attributes}->{$key}->{LocalName};
				last;
			    }
			}
		    }
		}
	    }
	}
    }

    foreach (keys %{$g->{groups}}) {
	$self->_process_templates($g->{groups}->{$_})
    }
}

# debug ----------------------------------------

sub _dump_g_stack {
    my $self = shift;

    print "G-stack:", 
      join('|',map("$_->{gid}",@{$self->{g_stack}})), "\n";
}

1;
__END__

=head1 NAME

XML::STX::Compiler - XML::STX stylesheet compiler

=head1 SYNOPSIS

no public API, used from XML::STX

=head1 AUTHOR

Petr Cimprich (Ginger Alliance), petr@gingerall.cz

=head1 SEE ALSO

XML::STX, perl(1).

no public API


=cut
