Aufbereitung von Visio-Netzplänen

Wie bereits erklärt, kann man aus Visio Netzplänen wunderbare Mehrwerte ziehen. Dazu müssen aber die Visio-Dateien vorbereitet werden. Und das sollte man automatisieren, weil sonst wieder keiner die Dokumentation pflegt.

Natürlich ist es möglich eine SVG Datei in Visio weiterzubearbeiten. Aber das will man oft nicht. Stattdessen werden SVG Exporte regelmäßig neu erzeugt, die alle möglichst schnell zu Weathermaps werden sollen. Das macht man nicht mehr manuell.

Phase 1: Vorbereitung

Zuerst läuft hier ein Vorbereitungsscript, das folgende Aufgaben zu erfüllen hat:

  • Um später mit den Verbindern ordentlich arbeiten zu können, müssen diese auf einer Zeile landen. Dieses Unfolding der Pfadinformation wird hier ausgeführt.
  • Um Interfacenamen den Geräten zuordnen zu können, müssen die Positionen der Geräte auf der Zeichnung ermittelt werden.
#!/usr/bin/perl

use strict;
use warnings;

my %group = ();
my @group_stack = ();
my $delayed_line;

while(<>) {
    # unfold path information
    if($delayed_line && s{^\s*}{}) {
      $_ = "$delayed_line $_";
      $delayed_line = undef;
    }
    if(m{<path.*[^>\s\r\n]\s*$}) {
      s/[\r\n]//g;
      $delayed_line = $_;
      next;
    }
    
    # Record group nesting and moved groups
    if(m{<g }) {
      my $id = "group $.";
      $id = $1 if m{ id="(\S+)"};
      if(m{ transform="translate\((-?[\d.]+),(-?[\d.]+)\)"}) {
        $group{$id}{'x'} = $1;
        $group{$id}{'y'} = $2;
      }
      unshift @group_stack, $id;
    }
    # Record named groups
    if(m{<desc>(.*)</desc>}) {
      $group{$group_stack[0]}{'desc'} = $1;
    }
    # Locate text and print it on channel #2
    if(m{textRect cx="(-?[\d.]+)" cy="(-?[\d.]+)"}) {
      my ($x, $y, $t) = ($1, $2);
      foreach my $id (@group_stack) {
        $t = $group{$id}{'desc'}
          if !defined $t && defined $group{$id}{'desc'};
        next unless defined $group{$id}{'x'};
        $x += $group{$id}{'x'};
        $y += $group{$id}{'y'};
      }
      printf STDERR "%d %d: %s\n", $x, $y, $t;
    }
    # Unnest group
    if(m{</g>}) {
      shift @group_stack;
    }

    # Print svg on channel #1
    print;
}

Die Liste der Geräte (aus stderr), die verwendet werden sollen, wird nun manuell nachbearbeitet. Es sind nur die Geräte drinnen zu lassen, die man braucht.

Phase 2: Netzplan anpassen

Als nächstes wird die so glattgezogenen SVG Datei mit Gradienten versehen und diese werden (meist) korrekt benannt. Im Einzelen geschieht folgendes:

  • Es werden alle <title> bis auf den des Gesamtbild entfernt.
  • Das Gesamtbild bekommt einen neuen <title>.
  • Exakt horizontale oder vertikale Linien werden leicht dejustiert, um eine Einfärbung überhaupt erst zu ermöglichen.
  • Die Verbinder werden eingefärbt. Im Gegensatz zur manuellen Richtungseinstellung wird hier in den jeweiligen Koordinaten des Verbinders ein Gradientenvektor definiert, der exakt von Anfangs- zum Endpunkt geht.
  • Die Richtung des Verbinders wird anhand der Lages der Beschriftung zu den Endpunkten des Verbinders ermittelt.
  • Die Gradienten pro Verbinder werden mit den Weathermap-Daten vorausgefüllt. Dabei werden die Kurzbeschreibungen expandiert und der Namen der nächstgelegenen Gerätes (aus Phase 1) hinzugefügt.
  • Die Bandbreite wird vorklassifiziert.
#! /usr/bin/perl

use strict;
use warnings;
use Data::Dumper;

my $main_title = shift;
unless ($main_title) {
    print "Usage: $0 'New title' [locationfile] < visio.svg > animatable.svg\n";
    exit;
}

sub distance($$$$) {
    my ($a,$b,$c,$d) = @_;
    my ($x, $y) = ($a-$c, $b-$d);
    return sqrt($x*$x + $y*$y);
}

my %devices = ();
my $locs = shift;
if(-r $locs) {
    open(L, "<$locs") || die "$locs: $!\n";
    while(<L>) {
      next unless /^(-?[\d.]+) (-?[\d.]+): (\S+)/;
      $devices{$3} = {'x' => $1, 'y' => $2};
    }
    close(L);
}

sub find_device($$) {
    my ($x,$y) = @_;
    
    my ($dev,$dist) = ("X",1000000);
    while(my ($d,$p) = each %devices) {
      local $_ = distance($x,$y,$p->{'x'},$p->{'y'});
      ($dev,$dist) = ($d,$_) if $dist > $_;
    }
    return $dev;
}


my @orig = ();
my %line = ();
my $title_seen = 0;
my $current_title = "";
my $current_desc  = "";
my ($text_x, $text_y);
my ($tx, $ty) = (0,0);
my ($x1, $x2, $y1, $y2);
while(<>) {
    if(m{<g }) {
      # Reset everything, new group
      $current_title = $current_desc = "";
      $x1 = $y1 = $x2 = $y2 = undef;
    }
    if(s{(<title>)(.*?)[.\d]*(</title>)}{$1$main_title$3}) {
      $current_title = $2;
      next if $title_seen++ > 0;
    }
    if(m{<g .*transform="translate\((-?[\d.]+),(-?[\d.]+)\)"}) {
      # record movements
      $tx = $1;
      $ty = $2;
    }
    $current_desc = $1 if m{<desc>(.*)</desc>};
    if($current_title eq "Dynamic connector") {
      if(m{textRect cx="(\S+)" cy="(\S+)"}) {
        $text_x = $1;
        $text_y = $2;
      }
      # deadjust horizonal or vertical lines
      s{(<path d="M(-?[\d.]+) (-?[\d.]+) L)\2( -?[\d.]+")}{$1.($2+0.01).$4}e;
      s{(<path d="M(-?[\d.]+) (-?[\d.]+) L(-?[\d.]+) )\3(")}{$1.($3+0.01).$5}e;
      if(m{<path.* d=\"\w(-?[.\d]+) (-?[.\d]+)}) {
        # Start of path
        $x1 = $1;
        $y1 = $2;
      }
      if(defined $x1 && m{\w(-?[.\d]+) (-?[.\d]+)\".*/>}) {
        # End of path
        $x2 = $1;
        $y2 = $2;
        s{/>}{ style="stroke:url(#line$.)"/>};

        my $mbps =
            $current_desc =~ s/^T\D*/TenGigabitEthernet/ ? "10000" :
            $current_desc =~ s/^P\D*/Port-channel/       ?  "2000" :
            $current_desc =~ s/^G\D*/GigabitEthernet/    ?  "1000" :
            $current_desc =~ s/^F\D*/FastEthernet/       ?   "100" :
            $current_desc =~ s/^E\D*/Ethernet/           ?    "10" :
            $current_desc =~ s/^S\D*/Serial/             ?     "2" :
                                                               "1" ;

        # Start where text is
        if(distance($text_x, $text_y, $x1, $y1) > distance($text_x, $text_y, $x2, $y2)) {
          ($x1, $x2, $y1, $y2) = ($x2, $x1, $y2, $y1);
        }
     
        my $dev = find_device($text_x+$tx, $text_y+$ty);
        print qq{  <!-- $dev:$current_desc ${mbps}000000 -->
  <linearGradient id="line$." gradientUnits="userSpaceOnUse" x1="$x1" x2="$x2" y1="$y1" y2="$y2">
    <stop offset="0" style="stop-color:#000000;stop-opacity:1"/>
    <stop offset=".1" style="stop-color:#00ff00;stop-opacity:1"/>
    <stop offset=".9" style="stop-color:#ff0000;stop-opacity:1"/>
    <stop offset="1" style="stop-color:#000000;stop-opacity:1"/>
  </linearGradient>
};
        $x1 = $y1 = $x2 = $y2 = undef;
      }
    }
    push @orig, $_;
    if(m{<defs id="Patterns_And_Gradients">}) {
      print join("",@orig);
      @orig = ();
    };
}

print "  <!-- Stop:Processing 0 -->\n";
print join("",@orig);

Nun sind nur noch sporadisch Nachbearbeitungen notwendig.

Damit sinkt der Zeitaufwand zur Veröffentlichung auch komplexerer Pläne auf wenige Minuten.

Post a comment

Related content