<!DOCTYPE HTML> <html lang="en"> <head> <title></title> <meta charset="utf-8" /> <meta name="description" content="" /> <meta name="keywords" content="" /> <!-- apple --> <meta name="apple-mobile-web-app-capable" content="yes" /> <meta name="viewport" content="user-scalable=yes,width=device-width" /> <link rel="apple-touch-startup-image" href="note_atsi.png" /> <link rel="apple-touch-icon" href="note_ati.png" /> <!-- stylesheets --> <link href="/" rel="stylesheet" type="text/css" /> <style></style> </head> <body> <div id="content" class=""> <a href="/" title="" target="_blank"></a> </div> <!-- Scripts --> <script type="text/javascript" src="/" ></script> </body> </html>
Posts in category XML and XSLT Examples
HTML 5 Template
Amazon Kindle: Export Notes and Highlights
Amazon has made the Kindle service accessible by making the software run on most modern devices. It’s still not quite as easy to annotate as a PDF — I’m not sure it will ever be as user friendly as Goodreader — but it sort of makes up for that by offering some cool stats on each book. You can see other peoples’ notes and highlights, see how the book ranks among Amazon’s most-highlighted, etc.
Kindle.Amazon.com is a companion site to the software that makes it possible to review and edit your notes for each of your books. Sign in. Select a book. Select your highlights. The only downside is that you can’t export them.
The script below is meant to be converted into a bookmarklet and added to your bookmarks toolbar, and it can then be executed on the page containing your notes. It will launch a new window containing your notes in XML format. Just copy and paste.
Source
(function() { var process = function(sb) { /* CONTENT */ sb = sb.toString().replace(/\[hl\]/g,'<span class="highlight">').replace(/\[\/hl\]/g,"</span>"); sb = sb.toString().replace(/\[nt\]/g,'<span class="note">').replace(/\[\/nt\]/g,"</span>"); sb = sb.toString().replace(/\[em\]/g,'<em>').replace(/\[\/em\]/g,"</em>"); sb = sb.toString().replace(/\[-h1\]/g, '<h1>').replace(/\[\/-h1\]/g, "</h1>"); sb = sb.toString().replace(/\[-h2\]/g, '<h2>').replace(/\[\/-h2\]/g, "</h2>"); sb = sb.toString().replace(/\[-h3\]/g, '<h3>').replace(/\[\/-h3\]/g, "</h3>"); sb = sb.toString().replace(/\[-h4\]/g, '<h4>').replace(/\[\/-h4\]/g, "</h4>"); /* MARKUP */ sb = sb.replace(/</g,"<").replace(/>/g,">").toString(); return sb.toString(); }; var title = jQuery('.title').text().trim(); var book = '<?php Header("Content-type: application/xml"); ?>'+ '<?xml version="1.0"?>' + '<?xml-stylesheet href="/xsl.php" type="text/xsl"?>'; var sb = ''; var o = "\n"+'<book title="'+title+'">'+title; var q1 = "\n"+'<quote type="highlight" >'; var q2 = '</quote>'; var c = "\n"+'</book>'; jQuery('div.highlightRow div.text').each(function() { var note = jQuery(this).find('span.noteContent,span.highlight'); sb = sb + q1+jQuery(note).text().toString().replace(/\n/g,"<br />")+q2; }); sb = process(book+o+sb+c); newWindow = window.open("","","status,height=700,width=500") newWindow.focus(); newWindow.document.write("<textarea>"+sb+"</textarea>"); newWindow.document.close(); })();
Bookmarklet
In some browsers, you can simply copy the bookmarklet into the URL and press return, and it will work. (It will only do its job if you’re on the page with your notes.) Some browsers force you to first manually create the bookmark by opening up your bookmarks and adding a new one.
javascript:(function%20()%20%7B%0A%20%20%20%20var%20process%20=%20function(sb)%20%7B%0A%20%20%20%20%0A%20%20%20%20sb%20=%20sb.toString().replace(/%5C%5Bhl%5C%5D/g,'%3Cspan%20class=%22highlight%22%3E').replace(/%5C%5B%5C/hl%5C%5D/g,%22%3C/span%3E%22);%0A%20%20%20%20sb%20=%20sb.toString().replace(/%5C%5Bnt%5C%5D/g,'%3Cspan%20class=%22note%22%3E').replace(/%5C%5B%5C/nt%5C%5D/g,%22%3C/span%3E%22);%0A%20%20%20%20sb%20=%20sb.toString().replace(/%5C%5Bem%5C%5D/g,'%3Cem%3E').replace(/%5C%5B%5C/em%5C%5D/g,%22%3C/em%3E%22);%0A%20%20%20%20%0A%20%20%20%20/*%20MARKUP%20*/%0A%20%20%20%20sb%20=%20sb.replace(/%3C/g,%22<%22).replace(/%3E/g,%22>%22).toString();%0A%20%20%20%20%0A%20%20%20%20return%20sb.toString();%0A%7D;%0A%0Avar%20title%20=%20jQuery('.title').text().trim();%0Avar%20book%20=%20'%3C?xml%20version=%221.0%22?%3E';%0Avar%20sb%20=%20'';%0Avar%20o%20=%20%22%5Cn%22+'%3Cbook%20title=%22'+title+'%22%3E'+title;%0Avar%20q1%20=%20%22%5Cn%22+'%3Cquote%20type=%22highlight%22%20%3E';%0Avar%20q2%20=%20'%3C/quote%3E';%0Avar%20c%20=%20%22%5Cn%22+'%3C/book%3E';%0AjQuery('div.highlightRow%20div.text').each(function()%20%7B%0A%20%20%20%20var%20note%20=%20jQuery(this).find('span.noteContent,span.highlight');%0A%20%20%20%20sb%20=%20sb%20+%20q1+jQuery(note).text().toString().replace(/%5Cn/g,%22%3Cbr%20/%3E%22)+q2;%0A%7D);%0A%0Asb%20=%20process(book+o+sb+c);%0A%0AnewWindow%20=%20window.open(%22%22,%22%22,%22status,height=700,width=500%22)%0AnewWindow.focus();%0AnewWindow.document.write(%22%3Cpre%3E%22+sb+%22%3C/pre%3E%22);%0AnewWindow.document.close();%0A%20%20%20%20%0A%7D)();
GoDaddy’s php.ini and .htaccess for parsing .xml files
The title says it all, doesn’t it? I’ve read plenty of web articles on this, and they’re all variants of the same thing. In a nutshell, you need to create a local php.ini file and a .htaccess file. The specific file names and their respective entries will vary slightly depending on what version of PHP your hosting account is using. (There are more than 2; Some accounts use both 4 and 5.)
My hosting account runs PHP5, primarily. My .htaccess file has the entry below. Note that it doesn’t have the -cgi (as in: AddHandler x-httpd-php5-cgi) suffix.
AddHandler x-httpd-php5 .xml
My php.ini, php4.ini, and php5.ini files (yes, I have all 3) include this single line:
short_open_tag = “0″
Now files with the .xml extension will be parsed by the PHP interpreter. You’ll need to remember to manually set the content-type header of your files if they’re not HTML. In my case, I had to set the content type header to text/xml.
I had a lot of files to add the line to, plus I wanted to gzip them, so the following command came in handy: (It should all be on one line.)
find *.pdf.xml -type f -exec sed -i '1i <?php /*Some PHP Stuff*/ ?>' {} \;
Goodreader Annotations (to XML with perl)
GoodReader is the best app on the iPad, period. It will search and export (via email) a book’s annotations now, so all that data can be put to use. The only downside is that all the annotation data can only be exported as raw text, which means I have to format into something else (like XML) to really exploit it.
#!/usr/bin/perl $xmlOpen = '<?xml version="1.0"?><?xml-stylesheet href="xsl/xsl.xsl" type="text/xsl"?>'; $xmlRoot = '<book annotated="$date" title="$title">$title'; $xmlClose = '</book>'; $xmlQuote = '<quote type="$type" page="$page" author="$author" >$quote</quote>'; @quotes = (); # ============================ # Annotations Parser # # This block opens the file (goodreader.txt) that would be created by pop.pl. # It reads in the contents, and joins all the \n into a single string so that it can # be operated on. # ============================ open(MYFILE, "<", "goodreader.txt"); binmode(MYFILE, ":utf8"); @lines = <MYFILE>; close(MYFILE); $content = join(/\n/,@lines); # ============================ # xmlRoot # # This gets the values for the root: book. The values are substituted into the string so that it can simply be print to the filehandle # ============================ ($title) = $content =~ m/File: (.*)$/m; $date = &getReadableTimeStamp(); $xmlRoot =~ s/\$title/$title/g; $xmlRoot =~ s/\$date/$date/g; #print $xmlRoot; $content =~ s/--- page (\d|\w)+ ---/SPLITPAGESPLIT\n$&/gi; @pages = split(/SPLITPAGESPLIT/,$content); shift(@pages); for $thisPage (@pages) { #$pages[1] will represent the first set of annotations ($pageNumber) = $thisPage =~ m/--- page (\d+|\w+) ---/gi; $thisPage =~ s/^(?=(note|highlight).*:$)/SPLITTYPESPLIT/gim; @annots = split(/SPLITTYPESPLIT/,$thisPage); shift(@annots); foreach $thisAnnot (@annots) { ($type) = $thisAnnot =~ m/^(note|highlight)(?=.*:)/gmi; ($author) = $thisAnnot =~ m/Highlight.*\b(\w+):$/im; $quote = $thisAnnot; $quote =~ s/^(note|highlight).*?:\n//ims; #Delete the annotation line $quote =~ s/\n+$//; #Delete the trailing newlines added by GoodReader #print '"'.$quote.'"'; #Escape Quote $quote = &escape($quote); #Clean up the quote (XML) $quote =~ s/&/&/gs; $quote =~ s/</</g; $quote =~ s/>/>/g; $quote =~ s/'/'/g; $quote =~ s/"/"/g; #Clean up the quote (MARKDOWN-like syntax) $quote =~ s/\n(?!.*\[br\])/<br \/>/gs; #If there are br, don't create newlines. $quote =~ s/\[hl\]/<span class="highlight">/g; $quote =~ s/\[nt\]/<span class="note">/g; $quote =~ s/\[\/(hl|nt)\]/<\/span>/g; $quote =~ s/\[br\]/<br \/>/g; $quote =~ s/\[em\]/<em>/g; $quote =~ s/\[\/em\]/<\/em>/g; push(@quotes,xmlQuote($type,$pageNumber,$author,$quote)); } } open(MYFILE, ">", "${title}.xml"); binmode(MYFILE, ":utf8"); print MYFILE $xmlOpen."\n".$xmlRoot."\n"; foreach $quote (@quotes) { print MYFILE $quote; } print MYFILE $xmlClose; close(MYFILE); `rm goodreader.txt`; print $title; exit 0; sub xmlQuote() { my ($type,$page,$author,$quote) = @_; return <<HERE; <quote type="$type" page="$page" author="$author" >$quote</quote> HERE } sub getReadableTimeStamp() { @months = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec); @weekDays = qw(Sun Mon Tue Wed Thu Fri Sat Sun); ($second, $minute, $hour, $dayOfMonth, $month, $yearOffset, $dayOfWeek, $dayOfYear, $daylightSavings) = localtime(); $year = 1900 + $yearOffset; $theTime = "$hour:$minute:$second, $weekDays[$dayOfWeek] $months[$month] $dayOfMonth, $year"; return $theTime; } sub escape() { ($line) = @_; $line =~ s/\x{0027}/'/ig; #UTF-8 Apostrophe $line =~ s/\x{2018}/'/ig; #UTF-8 Single Quote $line =~ s/\x{2019}/'/ig; #UTF-8 Single Quote $line =~ s/\x{201C}/\x{0022}/ig; #UTF-8 (?) Dbl Quote $line =~ s/\x{201D}/\x{0022}/ig; #UTF-8 (?) Dbl Quote #$line =~ s/”/"/ig; #HYPHEN (Dash) $line =~ s/\x{2011}/\x{002D}/ig; #UTF-8 $line =~ s/\x{2012}/\x{002D}/ig; #UTF-8 $line =~ s/\x{2013}/\x{002D}/ig; #UTF-8 #$line =~ s/\x{2122}/(TM)/ig; return $line; }
You can use the file above in conjunction with the one below. The file below will pull down a message from a pop server and write the message output to a file. With these two perl files, and GoodReader, you have a way to annotate and publish your notes.
#!/usr/bin/perl use Net::POP3; use URI::Escape; # Constructors $pop = Net::POP3->new('pop.popserver.com'); $username = 'you@youremail.com'; $password = 'password'; #$pop = Net::POP3->new('pop3host', Timeout => 60); if ($pop->login($username, $password) > 0) { my $msgnums = $pop->list; # hashref of msgnum => size foreach my $msgnum (keys %$msgnums) { $msg = $pop->get($msgnum); #print @$msg; &writeFile(); $pop->delete($msgnum); } } else { print "Cannot connect\n"; $pop->quit; exit 1; } $pop->quit; if (-e "goodreader.txt") { #$fileName = "No filename set"; $fileName = `perl parser.pl`; print $fileName; exit 0; } else { print "Cannot create file"; exit 1; } sub writeFile() { #Reformat message for output $content = join(/\rn|\n\r|\n|\r/,@$msg); $content =~ s/=\n//g; $content = &unescape($content); $content =~ s/^.*(?=File:)//gs; $content =~ s/^\(.*Reader\)$//gm; $content =~ s/ $//gm; #Parse out trailing spaces open(MYFILE, ">", "goodreader.txt"); #binmode(MYFILE, ":utf8"); #Causing errors on file write print MYFILE $content; close(MYFILE); } sub unescape() { my ($encoded) = @_; $encoded =~ s/=(?=(\w{1,2}|\d{1,2}))/%/g; my $decoded = uri_unescape($encoded); return $decoded; }
liferay-look-and-feel.xml
<!DOCTYPE look-and-feel PUBLIC "-//Liferay//DTD Look and Feel 6.0.0//EN" "http://www.liferay.com/dtd/liferay-look-and-feel_6_0_0.dtd"> <look-and-feel> <compatibility> <version>6.0.5+</version> <!-- VERSION, Compatibility is a required element --> </compatibility> <theme id="set" name="set"> <settings> <!-- NOTE THE COOL PROPERTY SETTING --> <setting key="portlet-setup-show-borders-default" value="false" /> </settings> </theme> <theme id="spike" name="spike"> <settings> <setting key="portlet-setup-show-borders-default" value="false" /> </settings> </theme> </look-and-feel> <!-- GET THE THEME NAME $themeDisplay.getTheme().getName() -->
liferay-layout-templates.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE layout-templates PUBLIC "-//Liferay//DTD Layout Templates 6.0.0//EN" "http://www.liferay.com/dtd/liferay-layout-templates_6_0_0.dtd"> <layout-templates> <custom> <layout-template id="ngw_nav_1_col" name="ngw_nav_1_col"> <template-path>/ngw_nav_1_col.tpl</template-path> <wap-template-path>/ngw_layout_group.wap.tpl</wap-template-path> <thumbnail-path>/ngw_layout_group.png</thumbnail-path> </layout-template> <!-- <layout-template id="ngw_layout_group" name="ngw-layout-group"> <template-path>/ngw_layout_group.tpl</template-path> <wap-template-path>/ngw_layout_group.wap.tpl</wap-template-path> <thumbnail-path>/ngw_layout_group.png</thumbnail-path> </layout-template> --> </custom> </layout-templates>
liferay-hook.xml
<?xml version="1.0"?> <!DOCTYPE hook PUBLIC "-//Liferay//DTD Hook 6.0.0//EN" "http://www.liferay.com/dtd/liferay-hook_6_0_0.dtd"> <hook> <custom-jsp-dir> /META-INF/ngw_layout_velocity_templates/ </custom-jsp-dir> </hook>
Liferay SDK Project Deployment Script
Liferay has a great SDK for use in creating plugins for their portal. I wrote the following script for deploying Liferay plugins (projects) to remote servers. This simplifies the Windows to *Nix transfer via SSH, and allows for deploying the same project to multiple servers, deploying different projects to the same (or different) servers, tailing logs, sending bash commands via CMD, and syncing the local system time with the remote server time* etc.
Calling the perl script below (called projects.pl) will load the XML file2, will build the project, upload it to a remote server (SFTP via WinSCP), and move it into the /deploy directory (ssh, via plink).
$user_path> projects.pl
Even if you’re not using Liferay, you can probably make use of this script if you’re using ANT, SSH and SFTP. The length of the script is due to the fact that many of the functions have been broken out so that functionality can be turned on/off very quickly, but that should make re-purposing it very easy.
Prerequisites:
ActiveState Perl
Putty (and plink.exe)
WinSCP
BWright.rar
Batch File1
Perl: projects.pl
#!/usr/bin/perl use XML::Simple; use Data::Dumper; use BWright::DateTime; use BWright::Clipboard; #Download BWright from http://localhost/resources/BWright.rar #There are a lot of dependencies if you use the other pm files in BWright.rar. ## GLOBALS $user = 'YOUR_USERNAME'; $pass = 'YOUR_PASSWORD'; # INCOMING ARGUMENTS (> deployment.pl tail 106) $arg = $ARGV[0]; $arg2 = $ARGV[1]; $arg3 = $ARGV[2]; # BEGIN PROCESSING $start_time = time; &setClip($pass); ## SET YOUR PASSWORD IN THE CLIPBOARD if ($arg =~ /tail/) { &doTail($arg2, $arg3); } elsif ($arg =~ /plink/) { &plink($arg3, $arg2); #command, #server } else { &loadProjectXML(); } ## FINISH print "nTasks Completen"; $end_time = time; $elapsed_time = $end_time - $start_time; print "nnElapsed time for all operations: $elapsed_time secondsn"; exit; # END PROCESSING sub loadProjectXML() { my $xml = new XML::Simple (KeyAttr=>[]); my $data = $xml->XMLin("projects.xml"); foreach $e (@{$data->{project}}) { # PROJECT ATTRIBUTES my $project = $e->{content}; #PROJECT NAME (Content of Project Node) my $deploy = $e->{deploy}; my $server = $e->{server}; my $rm_only = $e->{rm_only}; my $batch = $e->{batch}; my $local = $e->{p1}; my $remote = $e->{p2}; if ($deploy eq "yes") { if ($rm_only eq "yes") { ## Simply Remove the Context print "Removing Context for $project on $server. No deployment scheduled.n"; &remoteDelete($project, $remote, $server) } else { ##Build and Deploy print "nn----------------nDEPLOYING: ".uc $project." ON SERVER $servern----------------nn"; &deployment($project, $batch, $server, $local, $remote); } } else { print "Skipped: ".$server." for ".$project."n"; } } #VIEW OBJECT #print Dumper($data); } sub deployment($project, $batch_name, $server, $local, $remote) { my ($project, $batch_name, $server, $local, $remote) = @_; print "Removing contextsn"; &remoteDelete($project, $remote, $server); #print "Syncing System Timen"; #&updateSystemTime($server); print "Building Projectn"; &buildProject($batch_name); print "Uploadingn"; &winscp($project.'-5.2.0.1.war', $local, $server); #$file = project name #$delay = 1; #print "Copying to /deploy in $delay second(s)n"; #sleep $delay; &remoteMove($project.'-5.2.0.1.war', $remote, $server); #file = project name #&cleanWarDirectory($server); return 1; } sub plink($command, $server, $capture) { my ($command, $server, $capture) = @_; #DEFAULT FIRST 3 OCTETS OF IP SET IN COMMAND my $system_command = 'plink -t -pw '.$pass.' '.$user.'@XX.XX.XX.'.$server.' "'.$command.'"'; #USING BATCH COMMANDS INLINE (PLINK.EXE) if ($capture == 1) { return `$system_command`; } else { system $system_command; } #print $system_command."n"; return 1; } sub winscp($project, $local, $server) { my ($project, $local, $server) = @_; #/console launches the pop-up window with transfer stats #/command is like -batch, quiet operation my $system_command = 'winscp.exe /command "option batch on" "option confirm off" "open '.$user.':'.$pass.'$@XX.XX.XX.'.$server.'" "option transfer binary" "cd war" "put C:pathprojects\'.$local.'dist\'.$project.'" "close" "exit"'; system $system_command; return 1; } sub remoteMove($project, $remote, $server) { my ($project, $remote, $server) = @_; #PWD the DEPLOY directory my $directory = "/server_path/${remote}/deploy/"; #COPY THEMES from my WAR directory to DEPLOY! my $cmd = "echo '$pass' | sudo -S cp ~/war/$project ${directory}${project}"; #print $cmd."n"; &plink("$cmd", $server); #CHMOD permissions on EVERYTHING in DEPLOY $cmd = "echo '$pass' | sudo -S chmod 777 ${directory}*"; #print $cmd."n"; &plink("$cmd", $server); #LS the files in the DEPLOY directory $cmd = "echo '$pass' | sudo -S ls -l $directory"; #print $cmd."n"; &plink("$cmd", $server); return 1; }; sub remoteDelete($project, $remote, $server) { my ($project, $remote, $server) = @_; $cmd = "echo '$pass' | sudo rm -rf /server_path/${remote}/tomcat/webapps/$project"; #print $cmd."n"; &plink("$cmd", $server); $cmd = "echo '$pass' | sudo rm -rf /server_path/${remote}/tomcat/work/Catalina/localhost/$project"; #print $cmd."n"; &plink("$cmd", $server); return 1; } sub cleanWarDirectory($server) { my ($server) = @_; print "nCleaning up war directory on $servern"; &plink("sudo rm -rf ~/war/*", $server); return 1; } sub buildProject($batch_name) { my ($batch_name) = @_; my $system_call = "$batch_name & ant clean war"; #USING BATCH COMMANDS INLINE print "nn----------------nBuilding project: ".uc $batch_name."n----------------nn"; #print $system_call; system $system_call; return 1; } sub doTail($server, $remote) { my ($server, $remote) = @_; my $otd; #if (($remote eq "SOMEEXAMPLE")&&($server eq "106")) {$otd = '.'.&getYear().'-'.&getMonth(2).'-'.&getDay(3);} #CREATING A FILENAME DATE, IF NECESSARY &plink("sudo tail -f /server_path/$remote/tomcat/logs/catalina.out$otd; ".'$SHELL'." -i", $server); return 1; } sub updateSystemTime($server) { my ($server) = @_; my $new_hour = &plink('date +%H', $server, 1); chomp $new_hour; my $new_minute = &plink('date +%M', $server, 1); chomp $new_minute; $new_hour -= 2; system "time $new_hour:$new_minute:00.0"; print "Time set to: $new_hour:$new_minute:00.0; Synced with $servern"; return 1; }
The perl script reads in an xml file which contains various projects and the relative settings.
projects.xml
<?xml version='1.0'?> <projects> <!-- TIPS: Search and Replace to mass-edit Options Create batch files in your ENV Path so that you can quickly navigate to project directories. These batch files are used by projects.pl for executing ant, clean, war. --> <!-- STG 90 --> <project deploy='no' batch='some' server='90' p1='somepath' p2='someotherpath' rm_only='no'>some-theme</project> <project deploy='no' batch='sometpl' server='90' p1='somepath' p2='someotherpath' rm_only='no'>some-layouttpl</project> <!-- QA 30 --> <project deploy='no' batch='some' server='30' p1='somepath' p2='someotherpath' rm_only='no'>some-theme</project> <project deploy='no' batch='sometpl' server='30' p1='somepath' p2='someotherpath' rm_only='no'>some-layouttpl</project> </projects>
1Configure your Windows Environment Path variable to include a directory designated for custom batch scripts. Since I commonly navigate into multiple SDK projects throughout the day – often via the command line – I use short-named batch scripts that CD into the plugin directories for me. The batch files are literally nothing but a CD
2If some limiting options have not been set – the script also accepts options for tailing a remote log and sending simple SSH commands.
Perl References and a tiny pointer to some XML
I always forget: perl subroutines only accept scalars. That means you have to pass references for hashes and arrays to subroutines. Here is an example:
#!/usr/bin/perl #HASH PASS %hash1 = ( "Pass" => "Word" ); print "A: ".$hash1{"Pass"}."n"; &pass_the_hash(%hash1); sub pass_the_hash() { my %hash2 = %{@_[0]}; print "B: ".$hash2{Pass}."n"; return 1; }
XSL Transform using PowerShell
$xml = 'C:/article.xml' $xsl = 'C:/article.xsl' $output = 'C:/article_new.xml' if (-not $xml -or -not $xsl -or -not $output) { Write-Host "& .xslt.ps1 [-xml] xml-input [-xsl] xsl-input [-output] transform-output" exit; } trap [Exception] { Write-Host $_.Exception; } $xslt = New-Object System.Xml.Xsl.XslCompiledTransform; $xslt.Load($xsl); $xslt.Transform($xml, $output); Write-Host "generated" $output
