sysdeftools/validate/checklinks.pl
author Bob Rosenberg <bob.rosenberg@nokia.com>
Thu, 22 Jul 2010 12:30:22 +0100
branchHighFidelityModel
changeset 390 a47fc547d2e3
parent 319 b085ba15cdaa
permissions -rw-r--r--
Simplify and add hacks for some XSLT processors: simpler xpath expressions in filtering.xsl and qt hack in mergesysdef.xsl. Move path processing XSLT templates to separate module so it can be used in both joining and merging. Add .bat files for each of the PERL and XSLT scripts so they can be more esily called from the command line (joinsysdef.bat calls joinsysdef.pl, not joinsysdef.xsl).

# Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
# All rights reserved.
# This component and the accompanying materials are made available
# under the terms of "Eclipse Public License v1.0"
# which accompanies this distribution, and is available
# at the URL "http://www.eclipse.org/legal/epl-v10.html".
#
# Initial Contributors:
# Nokia Corporation - initial contribution.
#
# Contributors:
#
# Description:
# Script to validate the unit links in a system definition or package definition XML file
#!/usr/bin/perl

use strict;


use FindBin;		# for FindBin::Bin
use lib $FindBin::Bin;
use lib "$FindBin::Bin/lib";

use Cwd;
use Cwd 'abs_path';
use Getopt::Long;
use File::Basename;
use File::Spec;
use XML::DOM;

my $output;
my $path;
my %defineParams;
my %defines;
my $defaultns = 'http://www.symbian.org/system-definition';	# needed if no DTD
my $realloc;

# need to add options for controlling which metas are filtered out and which are included inline
GetOptions
	(
	'path=s' => $path,
	 'effective-sysdef=s' => \$realloc
	);

# -path specifies the full system-model path to the file which is being processed. 
#	This must be an absolute path if you're processing a root sysdef.
#	If processing a pkgdef file, you can use "./package_definition.xml" to leave all links relative. Though I can't really see the use case for this.


# if config is not set, no confguration will be done.
# If it is set, all configuration metadata will be processed and stripped from the output, even if the confguration data is empty

 if($path eq '') {$path = '/os/deviceplatformrelease/foundation_system/system_model/system_definition.xml'}

($#ARGV == -1 ) && &help();
my $sysdef = &abspath(shift);	# resolve the location of the root sysdef

$realloc = $realloc || $sysdef;

my %unitmap;
my @p1=reverse(split(/[\\\/]/,$path));
my @p2=reverse(split(/[\\\/]/,$realloc));

shift(@p1);shift(@p2); # don't care abt file name
while(lc($p1[0]) eq lc($p2[0])) {shift(@p1);shift(@p2)}

$unitmap{join('/',reverse(@p1))} = join("/",reverse(@p2));

my @p1=reverse(split(/[\\\/]/,$sysdef));
my @p2=reverse(split(/[\\\/]/,$realloc));

shift(@p1);shift(@p2); # don't care abt file name
while(lc($p1[0]) eq lc($p2[0]) && scalar(@p1)) {shift(@p1);shift(@p2)}

$unitmap{join('/',reverse(@p1))} = join("/",reverse(@p2));


# rootmap is a mapping from the filesystem to the paths in the doc
my %rootmap = &rootMap($path,$sysdef);	
my %nsmap;
my %urimap;

my $parser = new XML::DOM::Parser;
my   $sysdefdoc;
eval {
	$sysdefdoc = $parser->parsefile ($sysdef);
};
if(!$sysdefdoc) {
	die "ERROR: could not open $sysdef\n";
}



my $maxschema = $sysdefdoc->getDocumentElement()->getAttribute('schema');	# don't check value, just store it.

my $docroot =  $sysdefdoc->getDocumentElement;

my $ns = $docroot->getAttribute('id-namespace');
if(!$ns && $nsmap{''})
	{
	$docroot->setAttribute('id-namespace',$nsmap{''});
	}

$docroot->setAttribute('schema',$maxschema);	# output has the largest syntax version of all includes


while(my($pre,$uri) = each(%nsmap))
	{
	$pre ne '' || next ;
	$docroot->setAttribute("xmlns:$pre",$uri);
	}

&walk($sysdef,$docroot);	# process the XML

 
sub abspath
	{ 	# normalize the path into an absolute one
	my  ($name,$path) = fileparse($_[0]);
	$path=~tr,\\,/,;
	if( -e $path)
		{
		return abs_path($path)."/$name";
		}
	my @dir = split('/',$_[0]);
	my @new;
	foreach my $d (@dir)
		{
		if($d eq '.') {next}
		if($d eq '..')
			{
			pop(@new);
			next;
			}
		push(@new,$d)
		}
	return join('/',@new);
	}

sub rootMap {
	my @pathdirs = split(/\//,$_[0]);
	my @rootdirs = split(/\//,$_[1]);

	while(lc($rootdirs[$#rootdirs])  eq lc($pathdirs[$#pathdirs])  )
		{
		pop(@rootdirs);
		pop(@pathdirs);
		}
	return (join('/',@rootdirs)  => join('/',@pathdirs) );
	}

sub rootMapMeta {
	# find all the explict path mapping from the link-mapping metadata
	my $node = shift;
	foreach my $child (@{$node->getChildNodes})
			{
			if ($child->getNodeType==1 && $child->getTagName eq 'map-prefix')
				{
				my $from = $child->getAttribute('link');
				my $to = $child->getAttribute('to');		# optional, but blank if not set
				$rootmap{$from} = $to;
				}
			}
	# once this is processed we have no more need for it. Remove from output
	$node->getParentNode->removeChild($node);
	}


sub walk
	{ 	# walk through the doc, resolving all links
	my $file = shift;
	my $node = shift;
	my $type = $node->getNodeType;
	if($type!=1) {return}
	my $tag = $node->getTagName;
	if($tag=~/^(layer|package|collection|component)$/ )
		{
		my $link= $node->getAttribute('href');
		if($link)
			{
			my $file = &resolvePath($file,$link); 
			if(-e $file)
				{
				&combineLink($node,$file);
				}
			else
				{
				print  "Note: $file not found\n";
				$node->removeAttribute('href');
				}
			return;
			}
		}
	elsif($tag=~/^(SystemDefinition|systemModel)$/ )
		{
		}
	elsif($tag eq 'unit')
		{
		my %at = &atts($node);
		my $pro;
		foreach my $o (keys(%at)) 
			{
			if($o eq 'proFile' || $o=~/:proFile$/)
				{
				$pro = $at{$o};
				last;
				}
			}
		my $filter=$node->getParentNode()->getAttribute('filter');
		if($filter ne '' && $at{'filter'}) {$filter.=','.$at{'filter'}}
		elsif($at{'filter'}) {$filter=$at{'filter'}}
		if($filter ne '') {$filter="\t($filter)"}
		foreach my $atr ('bldFile','mrp','base')
			{
			my $ext;
			my $link= $at{$atr};
			if($atr eq 'bldFile') {
				$ext = ($pro ne '') ? "/$pro" : '/bld.inf'
			}
			if($link ne '')
				{
				my $ok = 0;
				my $trylink;
				if($link && !($link=~/^\//))
					{
					$link= &abspath(File::Basename::dirname($file)."/$link");
					$ok = (-e "$link$ext");
					if(!$ok)	
						{
						foreach my $a (keys %rootmap)
							{
							$link=~s,^$a,$rootmap{$a},ie;
							# remove leading ./  which is used to indicate that paths should remain relative
							$link=~s,^\./([^/]),$1,; 
							}

						}
					}
				if(!$ok)
					{
					foreach my $a (keys %unitmap) {
						if($a eq substr($link,0,length($a))) {
							$trylink = $unitmap{$a}.substr($link,length($a));
							if(-e "$trylink$ext") {
								$ok=1;
								$link = $trylink;
								last;
							}
						}
					}
					}
				if(!$ok)
					{
					print "Error: $atr not found in ",($trylink ne '') ? $trylink : $link,"$filter\n";
					}				
				}
			}
		}
	elsif($tag eq 'meta')
		{
		my $rel= $node->getAttribute('rel') || 'Generic';
		my $link= $node->getAttribute('href');
		$link=~s,^file://(/([a-z]:/))?,$2,; # convert file URI to absolute path
		if ($link ne '' ) 
			{ 
			if($link=~/^[\/]+:/)
				{
				print  "Note: Remote URL $link not validated\n";
				next; # do not alter children
				}
			if(! ($link=~/^\//))
				{
				$link= &abspath(File::Basename::dirname($file)."/$link");
				}
			if(! -e $link) 
				{
				if(! -e &realPath($link)) {
					print  "Warning: Local metadata file not found: $link\n";
				}
				next; # do not alter children
				}
			}
		if($node->getAttribute('rel') eq 'link-mapping')
			{# need to process this now
			&rootMapMeta($node);
			}
		return;
		}
	else {return}
	my $checkversion=0;
	foreach my $item (@{$node->getChildNodes})
		{
		#print $item->getNodeType,"\n";
		&walk($file,$item);
		}



	}


sub realPath
	{
	my $link = shift;
	foreach my $a (keys %unitmap)
		{
		if($a eq substr($link,0,length($a))) 
			{
			my $trylink = $unitmap{$a}.substr($link,length($a));
			if(-e $trylink) {return $trylink}
			}
		}
	}

sub combineLink
	{
	# combine data from linked sysdef fragment w/ equivalent element in parent document
	my $node = shift;
	my $file = shift;
	my $getfromfile = &localfile($file);
	$getfromfile eq '' && return;  # already raised warning, no need to repeat
	my $doc;
	eval {
		$doc = $parser->parsefile ($getfromfile);
	};
	if(!$doc) {
		print "ERROR: could not open $getfromfile\n";
		return;
	}
	my $item =&firstElement($doc->getDocumentElement);
	$item || die "badly formatted $file";	
	my @upid = &getNamespaceAndValue($node,'id');
	my @downid = &getNamespaceAndValue($item,'id');
	(($upid[0] eq $downid[0]) && ($upid[1] eq $downid[1]))  || die "$upid[1] ($upid[0]) differs from $downid[1] ($downid[0]) ";	# make sure the link is valid
	&walk($getfromfile,$item);
	}


sub copyInto
	{
	# make a deep copy the node (2nd arg) into the element (1st arg)
	my $parent=shift;
	my $item = shift;
	my $doc = $parent->getOwnerDocument;
	my $type = $item->getNodeType;
	my $new;
	if($type==1) 
		{
		$new = $doc->createElement($item->getTagName);
		my %down = &atts($item);
		foreach my $ordered ('id','name','bldFile','mrp','level','levels','introduced','deprecated','filter')
			{
			if($down{$ordered})
				{
				$new->setAttribute($ordered,$down{$ordered});
				delete $down{$ordered}
				}
			}
		while(my($a,$b) = each(%down))
			{
			$new->setAttribute($a,$b);
			}
		foreach my $child (@{$item->getChildNodes})
			{
			&copyInto($new,$child);
			}
		}
	elsif($type==3) 
		{
		$new = $doc->createTextNode ($item->getData);
		}
	elsif($type==8) 
		{
		$new = $doc->createComment  ($item->getData);
		}
	if($new)
		{
		$parent->appendChild($new);
		}
	}


sub getNamespaceAndValue
	{
	my $node = shift;
	my $attr = shift || 'id';
	my $id = $node->getAttribute($attr);
	if($id eq '') {return}
	my $ns;
	if($id=~s/^(.*)://)
		{ # it's got a ns, find out what it is
		$ns=&getNs($node,$1);
		}
	else
		{
		$ns = $node->getOwnerDocument->getDocumentElement->getAttribute("id-namespace") ||
			$defaultns;
		}
	return ($ns,$id);;
	}

sub getNs
	{
	# find the namespace URI that applies to the specified prefix.
	my $node = shift;
	my $pre = shift;
	my $uri = $node->getAttribute("xmlns:$pre");
	if($uri) {return $uri}
	my $parent = $node->getParentNode;
	if($parent && $parent->getNodeType==1)
		{
		return getNs($parent,$pre);
		}
	}



sub firstElement {
	# return the first element in this node
	my $node = shift;
	foreach my $item (@{$node->getChildNodes}) {
		if($item->getNodeType==1) {return $item}
	}
}


sub atts {
	# return a hash of all attribtues defined for this element
	my $node = shift;
	my %at = $node->getAttributes;
	my %list;
	foreach my $a (keys %{$node->getAttributes}) 
		{
		if($a ne '')
			{
			$list{$a} = $node->getAttribute ($a);
			}
		}
	return %list;
}


sub ns 
	{
	# return a hash of ns prefix and uri -- the xmlns: part is stripped off
	my $node = shift;
	my %list;
	foreach my $a (keys %{$node->getAttributes}) 
		{
		my $pre = $a;
		if($pre=~s/^xmlns://)
			{
			$list{$pre} = $node->getAttribute ($a);
			}
		}
	return %list;
	}


sub resolvePath
	{
	# return full path to 2nd arg relative to first (path or absolute URI)
	my $base = shift;
	my $path = shift;
	if($path=~m,^/,) {return $path } # path is absolute, but has no drive. Let OS deal with it.
	if($path=~s,^file:///([a-zA-Z]:/),$1,) {return $path } # file URI with drive letter
	if($path=~m,^file://,) {return $path } # file URI with no drive letter (unit-style). Just pass on as is with leading / and let OS deal with it
	if($path=~m,^[a-z0-9][a-z0-9]+:,i) {return $path } # absolute URI -- no idea how to handle, so just return
	return &abspath(File::Basename::dirname($base)."/$path");
	}


sub resolveURI
	{
	# return full path to 2nd arg relative to first (path or absolute URI)
	my $base = shift;
	my $path = shift;
	if($path=~m,[a-z0-9][a-z0-9]+:,i) {return $path } # absolute URI -- just return
	if($path=~m,^/,) {return $path } # path is absolute, but has no drive. Let OS deal with it.
	return &abspath(File::Basename::dirname($base)."/$path");
	}

sub localfile
	{
	my $file = shift;
	if($file=~s,file:///([a-zA-Z]:/),$1,) {return $file } # file URI with drive letter
	if($file=~m,file://,) {return $file } # file URI with no drive letter (unit-style). Just pass on as is with leading / and let OS deal with it
	if($file=~m,^([a-z0-9][a-z0-9]+):,i)
		{
		print  "ERROR: $1 scheme not supported\n";
		return;  # return empty string if not supported.
		} 
	return $file
	}



	

sub help
	{
	my $name= $0; $name=~s,^.*[\\/],,;
my $text;
format STDERR =
 ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
  $text,
     ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<~~
    $text
.
print STDERR "usage: $name  [options...] sysdef\n  valid options are:\n\n";
	foreach (
		"-path [sm-path]\tspecifies the full system-model path to the file which is being processed. By default this is  \"/os/deviceplatformrelease/foundation_system/system_model/system_definition.xml\"",
			"   This must be an absolute path if you're processing a root sysdef.",
			"   If processing a pkgdef file, you can use \"./package_definition.xml\" to leave all links relative.",
		"effective-sysdef [local-file]\tspecifies another local filesystem location the sysdef should be considered when resolving linked metas and unit paths, but not system model item hrefs. This is mainly used for testing system-wide changes to pkgdefs since it allows the pkgdefs to exist in a separate location to the rest of the codeline"
		) {
		$text = $_;
		write STDERR;
		print STDERR "\n";
	}

	exit(1);
	}