--- /dev/null
+#! /usr/bin/perl
+#
+# $Id$
+#
+# Normalize the .xbel file for cvs(1)/diff(1) etc.
+# Removes any variable data from the file.
+# Reencodes the file to utf-8.
+
+
+use strict;
+use warnings;
+
+require XML::Twig;
+use File::Temp qw(tempfile);
+
+
+my $opt_replace;
+# No &Getopt::Long::GetOptions as we need to distinguish '-i' and '-iext'.
+while (@ARGV) {
+ last if $ARGV[0]!~/^-/;
+ local $_=shift @ARGV;
+ if ($_ eq "-h" || $_ eq "--help") {
+ print <<"HERE";
+@{[ '$Id$' ]}
+
+Syntax: $0 [-i[<extension>]] [<file.xbel>]
+
+-i[<extension>] edit the file in place (makes backup if extension supplied)
+HERE
+ exit;
+ }
+ if (s/^-i//) {
+ $opt_replace=$_;
+ next;
+ }
+ if ($_ eq "--") {
+ last;
+ }
+ die "Invalid argument: $_\n";
+}
+die "Too many files\n" if 1<@ARGV;
+
+my $twig=XML::Twig->new(
+ "pretty_print"=>"indented",
+ "output_encoding"=>"utf-8",
+ "ignore_elts"=>{
+ "time_visited"=>1,
+ "time_added"=>1,
+ "time_modified"=>1,
+ "smart_site_data"=>1,
+ },
+ "twig_roots"=>{
+ "metadata"=>sub { $_->delete() if !$_->has_children(); },
+ "info" =>sub { $_->delete() if !$_->has_children(); },
+ "folder"=>sub { $_->del_att("folded"); },
+ "xbel" =>sub { $_->del_att("folded"); },
+ },
+);
+my $filename;
+if (!($filename=$ARGV[0])) {
+ $twig->parse(\*STDIN);
+ $twig->flush();
+ exit;
+}
+$twig->parsefile($filename);
+my $tmpname;
+my $dstfh;
+if (!defined $opt_replace) {
+ $dstfh=\*STDOUT;
+} else {
+ if (!$opt_replace) {
+ (undef(),$tmpname)=tempfile("DIR"=>".");
+ } elsif ($opt_replace=~/[*]/) {
+ ($tmpname=$opt_replace)=~s/[*]/$filename/g;
+ } else {
+ $tmpname=$filename.$opt_replace;
+ }
+ rename($filename,$tmpname) or die "Error renaming \"$filename\" to \"$tmpname\": $!";
+ open $dstfh,">",$filename or die "Error opening target file \"$filename\": $!";
+}
+$twig->flush($dstfh);
+if (defined $opt_replace) {
+ close $dstfh or die "Error closing target file \"$filename\": $!";
+ if (!$opt_replace) {
+ unlink $tmpname or die "Error deleting temporary file \"$tmpname\": $!";
+ }
+}