sysmodelgen bugfix when drawing collections next to nested packages. Major revision to validation script checklinks.pl (it's now reliable). joinsysdef.pl now reports errors on duplicate IDs. HighFidelityModel
authorBob Rosenberg <bob.rosenberg@nokia.com>
Tue, 18 May 2010 12:43:11 +0100
branchHighFidelityModel
changeset 209 af20ebf91ca6
parent 208 95cc05013e2e
child 210 bbb2d0977b1a
child 217 fcee787f22fe
sysmodelgen bugfix when drawing collections next to nested packages. Major revision to validation script checklinks.pl (it's now reliable). joinsysdef.pl now reports errors on duplicate IDs.
sysdeftools/joinsysdef.pl
sysdeftools/validate/checklinks.pl
sysmodelgen/core/draw.xsl
--- a/sysdeftools/joinsysdef.pl	Mon May 17 16:57:33 2010 +0100
+++ b/sysdeftools/joinsysdef.pl	Tue May 18 12:43:11 2010 +0100
@@ -35,6 +35,9 @@
 my %defineParams;
 my %defines;
 my $defaultns = 'http://www.symbian.org/system-definition';	# needed if no DTD
+my @excludeMetaList;
+my @cannotExclude= ('link-mapping', 'config');
+my %ID;	# list of all IDs
 
 my @newarg;
 foreach my $a (@ARGV)
@@ -55,7 +58,8 @@
 	(
 	 'path=s' => \$path,
 	'output=s' => \$output,
-	'config=s' => \$config
+	'config=s' => \$config,
+	'exclude-meta=s' => \@excludeMetaList
 	);
 
 # -path specifies the full system-model path to the file which is being processed. 
@@ -78,6 +82,13 @@
 my $sysdef = &abspath(shift);	# resolve the location of the root sysdef
 
 
+my %excludeMeta;
+foreach (@excludeMetaList) {$excludeMeta{$_}=1}	# make list a hash table
+foreach (@cannotExclude)
+	{
+	$excludeMeta{$_} && print STDERR "Error: Cannot exclude meta rel=\"$_\"\n";
+	$excludeMeta{$_}=0
+	}	# cannot exclude any of these rel types
 
 
 # rootmap is a mapping from the filesystem to the paths in the doc
@@ -233,9 +244,25 @@
 			else
 				{
 				print STDERR "Note: $file not found\n";
+				$node->removeAttribute('href');
 				}
 			return;
 			}
+		else 
+			{ # only check for duplicate IDs on the implementation
+			my $id= $node->getAttribute('id');
+			my $p = $node->getParentNode();
+			my $ptext = $p->getTagName()." \"".$p->getAttribute('id')."\"";
+			if(defined $ID{$id})
+				{
+				print STDERR "Error: duplicate ID: $tag \"$id\" in $ptext matches $ID{$id}\n";
+				}
+			else 
+				{
+				my $p = $node->getParentNode();
+				$ID{$id}="$tag in $ptext";
+				}
+			}
 		}
 	elsif($tag=~/^(SystemDefinition|systemModel)$/ )
 		{
@@ -259,6 +286,12 @@
 		}
 	elsif($tag eq 'meta')
 		{
+		my $rel= $node->getAttribute('rel') || 'Generic';
+		if($excludeMeta{$rel})
+			{
+			$node->getParentNode->removeChild($node);
+			return;
+			}
 		my $link= $node->getAttribute('href');
 		$link=~s,^file://(/([a-z]:/))?,$2,; # convert file URI to absolute path
 		if ($link ne '' ) 
@@ -584,7 +617,7 @@
 				return (@res,@docns);
 				#ignore any children nodes if this is a link
 				}
-			print STDERR "Note: $link not found\n";
+			# print STDERR "Note: $link not found\n";  -- no need to warm now. Do so later when trying to join
 			}
 		}
 	elsif($tag eq 'SystemDefinition' )
@@ -778,17 +811,30 @@
 sub help
 	{
 	my $name= $0; $name=~s,^.*[\\/],,;
-	print STDERR "usage: $name  [options...] sysdef\n\nvalid options are:\n",
-		"  -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\"\n",
-			"\t\tThis must be an absolute path if you're processing a root sysdef.\n",
-			"\t\tIf processing a pkgdef file, you can use \"./package_definition.xml\" to leave all links relative.\n\n",
+my $text;
+format STDERR =
+ ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
+  $text,
+     ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<~~
+    $text
+.
+print STDERR "usage: $name  [options...] sysdef\n  valid options are:\n\n";
+	foreach (
+		"-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.",
 
-		"  -output\tspecifies the file to save the output to. If not specified this will write to stdout\n\n",
+		"-output\tspecifies the file to save the output to. If not specified this will write to stdout",
 
-		"  -config\tspecifies the name of an .hrh file in which the configuration data is acquired from. If not set, no confguration will be done.\n",
-			"\t\tIf it is set, all configuration metadata will be processed and stripped from the output, even if the confguration data is empty\n\n",
-		"  -I[path]\tspecifies the include paths to use when resolving #includes in the .hrh file. This uses the same syntax as cpp command uses: a captial \"I\" followed by the path with no space in between. Any number of these can be provided.\n";
-
+		"-config\tspecifies the name of an .hrh file in which the configuration data is acquired from. If 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",
+		"-I[path]\tspecifies the include paths to use when resolving #includes in the .hrh file. This uses the same syntax as cpp command uses: a captial \"I\" followed by the path with no space in between. Any number of these can be provided.",
+		"-exclude-meta [rel]\tspecifies the 'rel' value of <meta> elements to exclude from the output. Any number of these can be provided. The following meta rel values affect the processing of the system definition and cannot be excluded: ".join(', ',@cannotExclude)
+		) {
+		$text = $_;
+		write STDERR;
+		print STDERR "\n";
+	}
 
 	exit(1);
 	}
--- a/sysdeftools/validate/checklinks.pl	Mon May 17 16:57:33 2010 +0100
+++ b/sysdeftools/validate/checklinks.pl	Tue May 18 12:43:11 2010 +0100
@@ -1,7 +1,7 @@
-# Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
+# 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 the License "Eclipse Public License v1.0"
+# 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".
 #
@@ -12,97 +12,523 @@
 #
 # Description:
 # Script to validate the unit links in a system definition or package definition XML file
+#!/usr/bin/perl
 
 use strict;
 
-if (! scalar @ARGV) {&help()}
+
+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])) {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 = $parser->parsefile ($sysdef);
+
+
+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);
+	}
 
 
-my $debug = 0;
-my $skipfilter;	# skip anything with a named filter
-my $xslt = "../../../buildtools/bldsystemtools/buildsystemtools/joinsysdef.xsl";
-my $xalan = "../../../buildtools/devlib/devlibhelp/tools/doc_tree/lib/apache/xalan.jar";
-my $sysdef = shift;
-while($sysdef=~/^-/) { #arguments
-	 if($sysdef eq '-nofilter') {$skipfilter = shift}
-	 elsif($sysdef eq '-v') {$debug = 1}
-	 else { &help("Invalid command line option $sysdef")} 
-	 $sysdef = shift; 
-}
-my $dir = $sysdef;
-$dir =~ s,[^\\/]+$,,;
-my $root="../../../..";
- my $full;
- 
-if($sysdef=~/system_definition\.xml/) {	# if running on a sysdef, ensure it's joined before continuing
-	($full = `java -jar $dir$xalan -in $sysdef -xsl $dir$xslt`) || die "bad XML syntax";
-}else {	# assume any other file has no hrefs to include (valid by convention)
-	$root='';
-	open S, $sysdef;
-	$full=join('',<S>);
-	close S;
-}
-$full=~s/<!--.*?-->//sg; # remove all comments;
-my $count=1;
+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;
+				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,; 
+							}
 
-my $filter = '';
-foreach (split(/</,$full)) {	# loop through all elements
-	my $found = 0;
-	if(/^component/) {		# save the current filter so we know if we need to skip the named filter
-		$filter='';
-		if(/filter="([^"]+)"/) {$filter=$1}
+						}
+					}
+				if(!$ok)
+					{
+					foreach my $a (keys %unitmap) {
+						if($a eq substr($link,0,length($a))) {
+							my $trylink = $unitmap{$a}.substr($link,length($a));
+							if(-e "$trylink$ext") {
+								$ok=1;
+								$link = $trylink;
+								last;
+							}
+						}
+					}
+					}
+				if(!$ok)
+					{
+					print "Error: $atr not found in $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);
+		}
+
+
+
 	}
-	elsif(s/^unit//) {
-		my $f=",$filter,";		# commas are the separators - safe to have extra ones for testing
-		if(/filter="([^"]+)"/) {$f.=",$1,"}
-		if($skipfilter ne '' && $f=~/,$filter,/) {next}	# don't test anything with s60 filter
-		if(/\smrp="(.*?)"/) {
-			my $file = &fileLocation($1);
-			if($debug) {print "MRP ",-s $file," $file\n"}		# debug code		
-			if(!(-s $file)){
-				print  STDERR "$count: Cannot find MRP file $file\n";	
-				$found=1;
+
+
+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}
 			}
 		}
-		if(/\sbldFile="(.*?)"/) {
-			my $file = &fileLocation("$1/bld.inf");
-			if($debug) {print "Bld ",-s $file ," $file\n"}		# debug code		
-			if(!(-s $file) ){
-				print  STDERR "$count: Cannot find bld.inf file $file\n";
-				$found=1;
+	}
+
+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 = $parser->parsefile ($getfromfile);
+	my $item =&firstElement($doc->getDocumentElement);
+	$item || die "badly formatted $file";	
+	&fixIDs($item);
+	my %up = &atts($node);
+	my %down = &atts($item);
+	$up{'id'} eq $down{'id'}  || die "$up{id} differs from $down{id}";
+	$node->removeAttribute('href');
+	foreach my $v (keys %up) {delete $down{$v}}
+	foreach my $v (keys %down)
+		{
+		$node->setAttribute($v,$down{$v})
+		}
+	foreach my $child (@{$item->getChildNodes})
+		{
+		&copyInto($node,$child);
+		}
+	&walk($file,$node);
+	}
+
+
+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) 
+		{
+		&fixIDs($item);
+		$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);
 			}
 		}
-		if(/\sbase="(.*?)"/) {
-			my $file = &fileLocation($1);
-			if($debug) {print "Base $file\n"}		# debug code		
-			if(!(-d $file) ){
-				print  STDERR "$count: Cannot find base dir $file\n";
-				$found=1;
-			}
+	elsif($type==3) 
+		{
+		$new = $doc->createTextNode ($item->getData);
+		}
+	elsif($type==8) 
+		{
+		$new = $doc->createComment  ($item->getData);
+		}
+	if($new)
+		{
+		$parent->appendChild($new);
 		}
-	}	
-	$count+=$found;	
+	}
+
+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 fixIDs
+	{
+	# translate the ID to use the root doc's namespaces 
+	my $node = shift;
+	foreach my $id ('id','before')
+		{
+		&fixID($node,$id);
+		}
 }
 
-exit $count;
+sub fixID
+	{
+	# translate the ID to use the root doc's namespaces 
+	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
+		my $pre = $1;
+		$ns=&getNs($node,$pre);
+		}
+	else
+		{
+		$ns = $node->getOwnerDocument->getDocumentElement->getAttribute("id-namespace") ||
+			$defaultns;
+		}
+	$ns = $urimap{$ns};
+	$id = ($ns eq '') ? $id : "$ns:$id";
+	return $node->setAttribute($attr,$id);
+}
 
-sub fileLocation {
-	my $file = "$dir$root$_[0]";
-	$file=~tr/\//\\/;
-	while($file=~s/\\[^\\.]+\\\.\.\\/\\/){}
-	return $file;
+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 help {
-	print "$0: ",($_[0] eq '' ? "syntax"  : $_[0]); 
-	print "\nSyntax: [-v] [-nofilter filter] system_definition.xml 
-Validate the unit links in a system definition or package definition XML
-file. This only prints errors in the files. If it exits silently, the links
-are all valid.
-	Call with -nos60 filter to skip checking presence of fitler=\"s60\" units
-	Requires system definition files to be in the standard location
-	in deviceplatformrelease,
-	and the presence of joinsysdef.xsl and xalan.jar in their expected
-	locations.
-	Package definition files can be anywhere.";
-exit 1;
-}
+
+
+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);
+	}
+
+
+	
--- a/sysmodelgen/core/draw.xsl	Mon May 17 16:57:33 2010 +0100
+++ b/sysmodelgen/core/draw.xsl	Tue May 18 12:43:11 2010 +0100
@@ -1484,7 +1484,6 @@
 <!-- ====== collections  ============= -->
 
 <xsl:template match="collection">
-
 <xsl:variable name="y" >
 	<xsl:choose>
 		<xsl:when test="ancestor::package/@levels or not(ancestor::layer/meta[@rel='model-levels'])">
@@ -1501,8 +1500,13 @@
 
 	<xsl:variable name="on-level" select="preceding-sibling::collection[(current()[not(@level)] and not(@level)) or @level=current()/@level]"/>
 	
+
 <xsl:variable name="x">
 	<xsl:choose>
+		<xsl:when test="../package and not(preceding-sibling::package)">
+			<!-- treat as if it's a normal collection-->
+			<xsl:value-of  select="sum($on-level/@width) + $groupDx * count($on-level) "/>
+		</xsl:when>
 		<xsl:when test="../package">
 			<xsl:call-template name="sum-list">
 				<xsl:with-param name="list">