Codebase list ptouch-driver / v1.6 foomaticalize
v1.6

Tree @v1.6 (Download .tar.gz)

foomaticalize @v1.6raw · history · blame

#! /usr/bin/perl

# Turn <select> declarations inside <printer> definitions into the equivalent
# <constraint> declarations in <option> definitions.
#
# In addition, printers not listed in the corresponding driver are added there.
#
# <select> declarations define which options a printer supports (or doesn't
# support).  In addition, for each option, a default value may be defined.  For
# options of type enum, the enumeration values the printer supports (or doesn'
# support) can be specified.  The syntax is as follows:
#
#   <select driver="name">
#     <option id="opt/id" sense="false">
#       <arg_defval>value</arg_defval>
#       <enum_val id="ev/id" sense="false" />
#     </option>
#   </select>
#
# The driver attribute is optional and defaults to the value of the <driver>
# tag.  The id attributes of the <option> and <enum_val> tags define the
# identifiers of the top-level <option> element and of the <enum_val> element
# within that option that the declaration refers to.
#
# The sense attributes are optional and default to "true".  When set to
# "false", the <option> or <enum_val> definition specifies that the printer
# doesn't support an option or enumeration value.  This can be useful for
# options or enumeration values that are declared to be supported by all
# printers of a particular driver by default.
#
# (There can be multiple <select> tags per <printer> tag.)

use strict;
use warnings;
use Getopt::Long qw(GetOptions);
use XML::LibXML;
use File::Basename;
use File::Path qw(make_path);

sub find_or_create_node($$) {
    my ($obj, $type) = @_;

    my $nodes = $obj->{node}->findnodes("./$type");
    return $nodes->[0]
	if ($nodes);
    my $node = $obj->{dom}->createElement($type);
    $obj->{node}->appendChild($node);
    return $node;
}

sub new_constraint($$$$$) {
    my ($where, $obj, $sense, $driver_name, $printer_id) = @_;

    my $constraints = find_or_create_node($obj, 'constraints');

    my $constraint = $obj->{dom}->createElement('constraint');
    $constraints->appendChild($constraint);
    $constraint->{sense} = $sense;
    my $driver = $obj->{dom}->createElement('driver');
    $driver->appendText($driver_name);
    $constraint->appendChild($driver);
    my $printer = $obj->{dom}->createElement('printer');
    $printer->appendText($printer_id);
    $constraint->appendChild($printer);
    return $constraint;
}

sub nodes_by_id($$) {
    my ($doms, $xpath_expression) = @_;

    my $nodes = {};
    foreach my $filename (keys %$doms) {
	my $dom = $doms->{$filename};
	foreach my $node ($dom->findnodes($xpath_expression)) {
	    my $id = $node->getAttribute('id');
	    die "$filename: Duplicate id $id\n"
		if exists $nodes->{$id};
	    $nodes->{$id} = {
		filename => $filename,
		node => $node,
		dom => $dom
	    };
	}
    }
    return $nodes;
}

sub nodes_by_name($$) {
    my ($doms, $xpath_expression) = @_;

    my $nodes = {};
    foreach my $filename (keys %$doms) {
	my $dom = $doms->{$filename};
	foreach my $node ($dom->findnodes($xpath_expression)) {
	    my $name = $node->findvalue('./name');
	    die "$filename: Duplicate name $name\n"
		if exists $nodes->{$name};
	    $nodes->{$name} = {
		filename => $filename,
		node => $node,
		dom => $dom
	    };
	}
    }
    return $nodes;
}

sub add_select_option ($$$) {
	my ($pri, $select_option, $opt) = @_;

	my $filename = $pri->{filename};
	my $opt_id = $select_option->getAttribute('id');
	my $select = $select_option->parentNode;
	my $printer = $select->parentNode;
	my $printer_id = $printer->getAttribute('id');

	my $driver_name = $select->getAttribute('driver');
	if (!defined $driver_name) {
	    $driver_name = $printer->findvalue('./driver')
		or die "$filename: No <driver> tag found\n";
	}

	my $sense = $select_option->getAttribute('sense') // 'true';
	my $constraint = new_constraint(
		"$filename: option '$opt_id'",
		$opt, $sense, $driver_name, $printer_id);

	my $arg_defvals = $select_option->findnodes('./arg_defval');
	if (@$arg_defvals) {
	    die "$filename: More than one <arg_defval> in option '$opt_id'\n"
		unless @$arg_defvals == 1;
	    my $arg_defval = $arg_defvals->[0];
	    # There should only be one <arg_defval>!
	    my $v = $opt->{dom}->createElement('arg_defval');
	    $v->appendText($arg_defval->firstChild);
	    $constraint->appendChild($v);
	}

	foreach my $select_ev ($select_option->findnodes('./enum_val')) {
	    my $ev_id = $select_ev->getAttribute('id');
	    my $sense = $select_ev->getAttribute('sense') // 'true';

	    my $evs = $opt->{node}->
		    findnodes("./enum_vals/enum_val[\@id='$ev_id']")
		or die "$filename: enumeration value '$ev_id' " .
		       "not found in option '$opt_id'\n";
	    foreach my $ev (@$evs) {
		my $obj = {
		    filename => $opt->{filename},
		    node => $ev,
		    dom => $opt->{dom}
		};
		new_constraint(
			"$filename: option '$opt_id', enum_val '$ev_id'",
			$obj, $sense, $driver_name, $printer_id);
	    }
	}
}

sub transform($) {
    my ($doms) = @_;

    my $drivers = nodes_by_name($doms, '/driver');
    my $options = nodes_by_id($doms, '/option');

    foreach my $filename (keys %$doms) {
	my $dom = $doms->{$filename};
	foreach my $printer ($dom->findnodes('/printer')) {
	    my $pri = {
		filename => $filename,
		node => $printer,
		dom => $dom
	    };
	    my $printer_id = $printer->getAttribute('id');
	    foreach my $select ($printer->findnodes('./select')) {
		foreach my $select_option ($select->findnodes('./option')) {
		    my $opt_id = $select_option->getAttribute('id');
		    my $opt = $options->{$opt_id}
			or die "$filename: '$opt_id' not defined\n";
		    add_select_option($pri, $select_option, $opt);
		}
		$printer->removeChild($select);
	    }

	    # If this printer isn't listed in its driver already, add it there.
	    my $driver_name = $printer->findvalue('./driver')
		or die "$filename: No <driver> tag found\n";
	    my $driver = $drivers->{$driver_name}
		or die "$filename: Driver $driver_name not found\n";
	    my $printers = find_or_create_node($driver, 'printers');

	    if (!$printers->findnodes("./printer[id='$printer_id']")) {
		my $printer = $driver->{dom}->createElement('printer');
		$printers->appendChild($printer);
		my $id = $driver->{dom}->createElement('id');
		$id->appendText($printer_id);
		$printer->appendChild($id);
	    }
	}
	foreach my $option ($dom->findnodes('/option')) {
	    next unless $option->findvalue('./arg_shortname/en') eq 'PageSize';
	    foreach my $enum ($option->findnodes('./enum_vals/enum_val')) {
		foreach my $driverval ($enum->findnodes('./ev_driverval')) {
		    # Until January 31, 2020, the foomatic parser didn't
		    # recognize values with fractional numbers like
		    # "9.6 2834.65" in PageSize values.  Until that fix is
		    # available widely enough, round the numbers.
		    if ($driverval->textContent =~
			/^\s*(\d+(?:\.\d+)?)\s+(\d+(?:\.\d+)?)\s*$/) {
			$driverval->removeChildNodes();
			$driverval->appendText(sprintf("%.0f", $1) . ' ' . sprintf("%.0f", $2));
		    }
		}
	    }
	}
    }
}

my $srcdir = '.';
my $out;
if (!GetOptions(
	'srcdir=s' => \$srcdir,
	'out=s' => \$out) || !defined $out) {
    die "Usage: $0 --out=DIR file.xml ...\n";
}

my $doms = {};

foreach my $filename (@ARGV) {
    my $dom = XML::LibXML->load_xml(location => "$srcdir/$filename", { no_blanks => 1 });
    $doms->{$filename} = $dom;
}

transform($doms);

foreach my $filename (keys %$doms) {
    my $string = $doms->{$filename}->toString(1);
    $string =~ s/^<\?.*\?>\s*/<!-- Generated by $ENV{PACKAGE_STRING} -->\n/;

    my $new_filename = "$out/$filename";
    my $directory = dirname($new_filename);
    make_path($directory);

    open(my $fh, '>', $new_filename);
    print $fh $string;
    close $fh;
}