The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
# Copyrights 2008-2018 by [Mark Overmeer <markov@cpan.org>].
#  For other contributors see ChangeLog.
# See the manual pages for details on the licensing terms.
# Pod stripped from pm file by OODoc 2.02.
# This code is part of distribution XML-Rewrite.  Meta-POD processed with
# OODoc into POD and HTML manual-pages.  See README.md
# Copyright Mark Overmeer.  Licensed under the same terms as Perl itself.

package XML::Rewrite::Schema;
use vars '$VERSION';
$VERSION = '0.11';

use base 'XML::Rewrite';

use warnings;
use strict;

use Log::Report 'xml-rewrite';

use XML::Compile::Util    qw/pack_type type_of_node :constants/;
use XML::LibXML           ();
use File::Spec::Functions qw/catfile/;
use File::Basename        qw/dirname/;


sub init($)
{   my ($self, $args) = @_;
    $self->SUPER::init($args);

    $self->{XRS_elemform} = $args->{element_form};
    $self->{XRS_attrform} = $args->{attribute_form};
    $self->{XRS_target}   = $args->{target_namespace};
    $self->{XRS_include}  = $args->{expand_includes};

    $self->importDefinitions( [SCHEMA2001, XMLNS] );

    # Something must be produced for an annotation: it's required
    $self->addHook( id => 'annotation'
                  , replace => sub { ($_[3] => XML::LibXML::Text->new('')) } )
        if $args->{remove_annotations};

    $self->addHook( id => [ qw/key keyref unique/ ]
                  , replace => sub { ($_[3] => XML::LibXML::Text->new('')) } )
        if $args->{remove_identity_constraints};

    $self;
}


sub repairXML($$$)
{   my ($self, $type, $schema, $details) = @_;

    $self->SUPER::repairXML($type, $schema, $details);
    $self->repairSchemaHeader($schema);
    $self;
}

sub repairSchemaHeader($)
{   my ($self, $schema) = @_;
    # we cannot interfere with the parser directly, so will need
    # modify the XML tree.
    if(my $elemform = $self->{XRS_elemform})
    {   $schema->removeAttribute('elementFormDefault');
        $schema->setAttribute(elementFormDefault => $elemform);
    }

    if(my $attrform = $self->{XRS_attrform})
    {   $schema->removeAttribute('attributeFormDefault');
        $schema->setAttribute(elementFormDefault => $attrform);
    }

    if(my $target = $self->{XRS_target})
    {   $schema->removeAttribute('targetNamespace');
        $schema->setAttribute(targetNamespace => $target);
    }
}

sub transformData($$$)
{   my ($self, $type, $data, $details) = @_;
    $self->SUPER::transformData($type, $data, $details);
    $self->transformForm($data);
    $self->expandIncludes($data, $details) if $self->{XRS_include};
    $self;
}

sub transformForm($)
{   my ($self, $data) = @_;

    if(my $elemform = $self->{XRS_elemform})
    {    $elemform eq 'unqualified' || $elemform eq 'qualified'
         or error __x"element for must be qualied or unqualified, not {form}"
              , form => $elemform;
         $data->{elementFormDefault} = $elemform;
    }

    if(my $attrform = $self->{XRS_attrform})
    {    $attrform eq 'unqualified' || $attrform eq 'qualified'
         or error __x"attribute for must be qualied or unqualified, not {form}"
              , form => $attrform;
         $data->{attributeFormDefault} = $attrform;
    }

    if(my $target = $self->{XRS_target})
    {   $data->{targetNamespace} = $target;
    }

    $self;
}

sub expandIncludes($$$)
{   my ($self, $data, $details) = @_;
    my $prefixes = $self->prefixes;
    my $xsd      = $prefixes->{&SCHEMA2001}{prefix};
    $xsd        .= '_' if length $xsd;

    my %included;
    my $cho_in = $data->{"cho_${xsd}include"} or return;
    my @cho_in = @$cho_in;
    my @cho_out;
    my @toplevel = @{$data->{"seq_${xsd}schemaTop"} || []};
    foreach my $incl (@cho_in)
    {   my ($kind, $def) = %$incl;
        if($kind ne "${xsd}include")
        {   push @cho_out, $incl;
            next;
        }

        my $basefn = $details->{filename};
        defined $basefn
            or error __x"includes need base filename";

        my $relfn  = $def->{schemaLocation} or next;
        my $inclfn = catfile dirname($basefn), $relfn;

        if($included{$inclfn}++)
        {   trace "include file $inclfn already seen";
            next;
        }

        my $doc    = eval { XML::LibXML->new->parse_file($inclfn) };
        if($@)
        {   notice __x"include file {fn} cannot be used", fn => $inclfn;
            push @cho_out, $incl;
            next;
        }
        trace "including {$inclfn}";

        my $xml     = $doc->documentElement;
        my $type    = type_of_node $xml;
        $type eq pack_type(SCHEMA2001, 'schema')
            or error __x"file {fn} does not contain a schema", fn => $type;

        # gladly, the reader will use the same prefix table!
        my $include = $self->reader($type)->($xml);

        # include location should be rewritten, but usually ok
        unshift @cho_in,   @{$include->{"cho_${xsd}include"}   || []};
        unshift @toplevel, @{$include->{"seq_${xsd}schemaTop"} || []};
    }
    $data->{"cho_${xsd}include"}   = \@cho_out;
    $data->{"seq_${xsd}schemaTop"} = \@toplevel;
}

1;