Well, editing XMLs in the text itself is a boring task. Mentally visualizing it is quite hard sometimes. So i wanted to write a node-centric XML Viewer/Editor which would allow me to make a better visualization of semi-structured XMLs.

I decided to craft something in Gtk2 and ended with the following code...

This is the start for what the editor will looks like, and the good news is that you can use the navigator as a widget in any application, as it's a Perl/Glib Object. It works nice for complex XMLs, but I think it would suffer with big XMLs as, for some reason I can't explain, I choosed to use XML::DOM.

Well, the first snippet is the module that implements the widget XMLNavigator. The second snippet is a program that uses it.

The way you can use it is: 1) click on a node to put it into the center. 2) for children of the center node, you can use the mouse scroll to navigate through them. Have fun.

The Module (with the patch from ForgotPasswordAgain's comment below):

package XMLNavigator; use strict; use warnings; use Gtk2; use XML::DOM; use POSIX qw(ceil); use constant DEFAULT_COLLAPSED_BOX_WIDTH => 75; use constant DEFAULT_COLLAPSED_BOX_HEIGHT => 45; use constant DEFAULT_FULL_BOX_HEIGHT => 155; use constant DEFAULT_FULL_BOX_WIDTH => 160; use constant DEFAULT_INSETS => 40; use constant RETICENCIAS_WIDTH => 15; use constant RETICENCIAS_HEIGHT => 5; use Glib::Object::Subclass 'Gtk2::DrawingArea', signals => { model_changed => { method => 'do_model_changed', flags => [qw/run-first/], return_type => undef, param_types => [] } }, properties => [ Glib::ParamSpec->scalar ('domdocument', 'domdocument', 'Object containing the DOM Document', [qw/readable writable/]), Glib::ParamSpec->int ('collapsed_box_width', 'collapsed_box_width', 'Width of the collapsed box', 0, 500, DEFAULT_COLLAPSED_BOX_WIDTH, [qw/readable writable/]), Glib::ParamSpec->int ('collapsed_box_height', 'collapsed_box_height', 'Height of the collapsed box', 0, 500, DEFAULT_COLLAPSED_BOX_HEIGHT, [qw/readable writable/]), Glib::ParamSpec->int ('full_box_height', 'full_box_height', 'Height of the full box', 0, 500, DEFAULT_FULL_BOX_HEIGHT, [qw/readable writable/]), Glib::ParamSpec->int ('full_box_width', 'full_box_width', 'Width of the full box', 0, 500, DEFAULT_FULL_BOX_WIDTH, [qw/readable writable/]), Glib::ParamSpec->int ('insets', 'insets', 'Insets between boxes', 0, 500, DEFAULT_FULL_BOX_WIDTH, [qw/readable writable/]), Glib::ParamSpec->int ('max_hori_distance', 'max_hori_distance', 'Maximum Horizontal Distance', 0, 500, 3, [qw/readable writable/]), Glib::ParamSpec->int ('max_vert_distance', 'max_vert_distance', 'Maximum Vertical Distance', 0, 500, 3, [qw/readable writable/]) ]; sub INIT_INSTANCE { my $self = shift; $self->{domdocument} = undef; $self->{matrix} = {}; $self->{collapsed_box_height} = DEFAULT_COLLAPSED_BOX_HEIGHT; $self->{collapsed_box_width} = DEFAULT_COLLAPSED_BOX_WIDTH; $self->{full_box_width} = DEFAULT_FULL_BOX_WIDTH; $self->{full_box_height} = DEFAULT_FULL_BOX_HEIGHT; $self->{max_vert_distance} = 3; $self->{max_hori_distance} = 3; $self->{insets} = DEFAULT_INSETS; $self->signal_connect(button_press_event => \&button_press_eve +nt); $self->signal_connect(expose_event => \&expose_event); $self->signal_connect(configure_event => \&configure_event); $self->signal_connect(size_request => \&do_size_request); $self->signal_connect(scroll_event => \&scroll_event); $self->set_events ([qw(exposure-mask leave-notify-mask button-press-mask button-release-mask scroll-mask)]); } sub GET_PROPERTY { my ($self, $pspec) = @_; if (exists $self->{$pspec->get_name()}) { return $self->{$pspec->get_name()}; } } sub SET_PROPERTY { my ($self, $pspec, $newval) = @_; if (exists $self->{$pspec->get_name()}) { $self->{$pspec->get_name()} = $newval; } if ($pspec->get_name eq 'domdocument') { $self->{matrix} = {}; $self->plan_matrix(); $self->queue_draw(); } elsif ($pspec->get_name eq 'matrix') { $self->plan_matrix(); $self->queue_draw(); } } sub do_model_changed { } sub plan_matrix { my $self = shift; my $max_hori_distance = $self->{max_hori_distance}; my $max_vert_distance = $self->{max_vert_distance}; my $xmlthink_center = $self->{matrix}{0}{0}; my %center_on_row = (); foreach my $key (keys %{$self->{matrix}}) { $center_on_row{$key} = $self->{matrix}{$key}{0}; } $self->{matrix} = {}; if (not defined $self->{domdocument}) { return; } my $xmlthink_root = $self->{domdocument}->getDocumentElement() +; if (not defined $xmlthink_root) { return; } if (not defined $xmlthink_center) { $xmlthink_center = $xmlthink_root; } #print "0, 0 = $xmlthink_center\n"; $self->{matrix}{0}{0} = $xmlthink_center; HORIZONTAL_BW: for my $hori_distance (1..$max_hori_distance+1) { $hori_distance *= -1; my $elem = $self->{matrix}{$hori_distance + 1}{0}; my $other = $elem->getParentNode(); if ($other && $other != $self->{domdocument}) { #print "$hori_distance, 0 = $other\n"; $self->{matrix}{$hori_distance}{0} = $other; } else { last HORIZONTAL_BW; } for my $sign_v (-1, 1) { VERTICAL_BW: for my $vert_distance (1..$max_vert_distance) +{ $vert_distance *= $sign_v; my $other = $self->{matrix}{$hori_dist +ance + 1}{$vert_distance - $sign_v}; my $brot = $sign_v<0?$other->getPrevio +usSibling():$other->getNextSibling(); while (defined $brot && $brot->getNode +Type() != XML::DOM::ELEMENT_NODE) { $brot = $sign_v<0?$brot->getPr +eviousSibling():$brot->getNextSibling(); } if ($brot) { #print "".($hori_distance+1)." +, $vert_distance = $brot\n"; $self->{matrix}{$hori_distance + + 1}{$vert_distance} = $brot; my @list = grep { $_->getNodeT +ype() == XML::DOM::ELEMENT_NODE } $brot->getChildNodes(); if ($#list >= 0) { my $la = $hori_distanc +e+1.0001; $la =~ s/,/./g; #print "$la, $vert_dis +tance = undef\n"; $self->{matrix}{$la}{$ +vert_distance} = undef } } else { last VERTICAL_BW; } if ($hori_distance == -1) { if (abs($vert_distance) == $ma +x_vert_distance -1) { my $obrot = $sign_v<0? +$other->getPreviousSibling():$other->getNextSibling(); if ($obrot) { #print "".($ho +ri_distance+1).", ".($vert_distance + $sign_v)." = undef\n"; $self->{matrix +}{$hori_distance + 1}{$vert_distance + $sign_v} = undef; } last VERTICAL_BW; } } else { if (abs($vert_distance) == $ma +x_vert_distance) { my $obrot = $sign_v<0? +$other->getPreviousSibling():$other->getNextSibling(); if ($obrot) { #print "".($ho +ri_distance+1).", ".($vert_distance + $sign_v)." = undef\n"; $self->{matrix +}{$hori_distance + 1}{$vert_distance + $sign_v} = undef; } } } } } if (abs($hori_distance) == $max_hori_distance + 1) { my $obrot = $other->getParentNode(); #print "$hori_distance, 0 = undef\n"; $self->{matrix}{$hori_distance}{0} = undef; } } HORIZONTAL_FW: for my $hori_distance (1..$max_hori_distance) { my $elem = $self->{matrix}{$hori_distance - 1}{0}; last HORIZONTAL_FW unless defined $elem; my @list = grep { $_->getNodeType() == XML::DOM::ELEME +NT_NODE } $elem->getChildNodes(); last HORIZONTAL_FW unless $#list >= 0; if (not $center_on_row{$hori_distance}) { $center_on_row{$hori_distance} = $list[ceil($# +list/2)]; } my $obj = $center_on_row{$hori_distance}; #print "$hori_distance, 0 = $obj\n"; $self->{matrix}{$hori_distance}{0} = $obj; for my $sign_v (-1, 1) { VERTICAL_FW: for my $vert_distance (1..$max_vert_distance) +{ $vert_distance *= $sign_v; my $other = $self->{matrix}{$hori_dist +ance}{$vert_distance - $sign_v}; my $brot = $sign_v<0?$other->getPrevio +usSibling():$other->getNextSibling(); while (defined $brot && $brot->getNode +Type() != XML::DOM::ELEMENT_NODE) { $brot = $sign_v<0?$brot->getPr +eviousSibling():$brot->getNextSibling(); } if ($brot) { #print "".($hori_distance).", +$vert_distance = $brot\n"; $self->{matrix}{$hori_distance +}{$vert_distance} = $brot; if (grep { $_->getNodeType() = += XML::DOM::ELEMENT_NODE } $brot->getChildNodes()) { my $la = $hori_distanc +e+0.0001; $la =~ s/,/./g; #print "".($la).", $ve +rt_distance = undef\n"; $self->{matrix}{$la}{$ +vert_distance} = undef } } else { last VERTICAL_FW; } if (abs($vert_distance) == $max_vert_d +istance) { my $obrot = $sign_v<0?$other-> +getPreviousSibling():$other->getNextSibling(); if ($obrot) { #print "".($hori_dista +nce).", ".($vert_distance+$sign_v)." = $brot\n"; $self->{matrix}{$hori_ +distance}{$vert_distance + $sign_v} = undef; } } } } if (abs($hori_distance) == $max_hori_distance) { my @list = grep { $_->getNodeType() == XML::DO +M::ELEMENT_NODE } $elem->getChildNodes(); if ($#list >= 0) { #print "".($hori_distance+1).", 0 = un +def\n"; $self->{matrix}{$hori_distance+1}{0} = + undef; } } } $self->drawit_all(); } sub do_size_request { my ($self, $requisition) = @_; $requisition->width ($self->{full_box_width}); $requisition->height ($self->{full_box_height}); } sub configure_event { my ($self, $event) = @_; $self->{pixmap} = Gtk2::Gdk::Pixmap->new ($self->window, $self->allocation->width, $self->allocation->height, -1); # same depth as window $self->{max_vert_distance} = int((($self->allocation->height/2 +)-($self->{full_box_height}/2)-($self->{insets}/2))/($self->{collapse +d_box_height}+$self->{insets})) || 1; $self->{max_hori_distance} = int((($self->allocation->width/2) +-($self->{full_box_width}/2)-($self->{insets}/2))/($self->{collapsed_ +box_width}+$self->{insets})) || 1; $self->plan_matrix(); } sub expose_event { my ($self, $event) = @_; $self->window->draw_drawable ($self->style->fg_gc($self->state), $self->{pixmap}, $event->area->x, $event->area->y, $event->area->x, $event->area->y, $event->area->width, $event->area->height); } sub drawit_all { my $self = shift; return unless $self->{pixmap}; $self->drawit_in(0, 0, $self->allocation->width(), $self->allo +cation->height()); } sub drawit_in { my $self = shift; my ($x, $y, $w, $h) = @_; my $gc = $self->style->fg_gc($self->state); $gc->set_clip_rectangle(Gtk2::Gdk::Rectangle->new($x, $y, $w, +$h)); $gc->set_rgb_fg_color(Gtk2::Gdk::Color->new(255*257, 255*257, +255*257)); $self->{pixmap}->draw_rectangle($gc, 1, 0, 0, $w, $h); $gc->set_rgb_fg_color(Gtk2::Gdk::Color->new(0, 0, 0)); $self->draw_data($self->{pixmap}, $gc); $gc->set_clip_rectangle(undef); $self->queue_draw(); } sub draw_data { my $self = shift; my ($area, $gc) = @_; my $max_hori_distance = $self->{max_hori_distance}; my $max_vert_distance = $self->{max_vert_distance}; my $xmlthink_doc = $self->{domdocument}; return unless $xmlthink_doc; my $xmlthink_root = $xmlthink_doc->getDocumentElement(); my $xmlthink_center = $self->{matrix}{0}{0}; if (not defined $xmlthink_center) { $self->plan_matrix(); $xmlthink_center = $self->{matrix}{0}{0}; return unless $xmlthink_center; } my $canvas_width = $self->allocation->width; my $canvas_height = $self->allocation->height; my $center_x = int($canvas_width/2); my $center_y = int($canvas_height/2); my $fullb_w = $self->{full_box_width}; my $fullb_h = $self->{full_box_height}; my $collb_w = $self->{collapsed_box_width}; my $collb_h = $self->{collapsed_box_height}; my $insets = $self->{insets}; $self->draw_center_element($area, $gc); if ($xmlthink_root != $xmlthink_center) { $self->draw_line_to_parent($area, $gc, $center_x - int +($insets/2) - int($fullb_w/2) + 1, $center_y - int($insets/2)); } for my $hori (sort {$a <=> $b} keys %{$self->{matrix}}) { for my $vert (sort {$a <=> $b} keys %{$self->{matrix}{ +$hori}}) { next if ($hori == 0 && $vert == 0); my $obj = $self->{matrix}{$hori}{$vert}; my $abs_distance_x = (abs($hori) * ($collb_w + + $insets)); my $abs_distance_y = (abs($vert) * ($collb_h + + $insets)); $abs_distance_x += int(($fullb_w - $collb_w)/2 +); if (abs($hori)<0.1) { # Center Column !! $abs_distance_y += int(($fullb_h - $co +llb_h)/2); } my $distance_x = $abs_distance_x * ($hori>=1?1 +:-1); my $distance_y = $abs_distance_y * ($vert==0?0 +:$vert>0?1:-1); my $box_center_x = $center_x + $distance_x; my $box_center_y = $center_y + $distance_y; my $x = $box_center_x - (int($collb_w/2)+int($ +insets/2)); my $y = $box_center_y - (int($collb_h/2)+int($ +insets/2)); if (exists $self->{matrix}{$hori - 1} && int($ +hori)==$hori) { $self->draw_line_to_parent($area, $gc, + $x, int($y+$collb_h/2)); } if ($obj) { $self->draw_collapsed_element($area, $ +gc, $obj, $x, $y); } else { if (int($hori)!=$hori) { # side reticencia $x = $box_center_x + (int($col +lb_w/2)-int($insets/2)); $y = $box_center_y - (int($col +lb_h/2)+int($insets/2)); $self->draw_reticencia($area, +$gc, $x+2, $y); } else { # element reticencia $x = $box_center_x - (int($col +lb_w / 2)+int($insets/2)); $y = $box_center_y - (int(RETI +CENCIAS_HEIGHT / 2)+int($insets/2)); $self->draw_reticencia($area, +$gc, $x, $y); } } } } $self->queue_draw(); } sub draw_center_element { my ($self, $area, $gc) = @_; my $canvas_width = $self->allocation->width; my $canvas_height = $self->allocation->height; my $pangoc = $self->get_pango_context(); my $fontdesc = Gtk2::Pango::FontDescription->from_string("Sans + 10"); my $xmlthink_center = $self->{matrix}{0}{0}; my $center_x = int($canvas_width/2); my $center_y = int($canvas_height/2); my $fullb_w = $self->{full_box_width}; my $fullb_h = $self->{full_box_height}; my $insets = $self->{insets}; my $x = $center_x - int($fullb_w/2) - int($insets/2); my $y = $center_y - int($fullb_h/2) - int($insets/2); # BIG RECTANGLE $area->draw_rectangle($gc, 0, $x, $y, $fullb_w, $fullb_h); # TAG my $tag_rect = Gtk2::Gdk::Rectangle->new($x+2, $y+2, $fullb_w- +4, 20); $area->draw_rectangle($gc, 0, $tag_rect->x, $tag_rect->y, $tag +_rect->width, $tag_rect->height); $self->draw_text($gc, $xmlthink_center->getTagName(), $tag_rec +t->x+2, $tag_rect->y+2, $tag_rect->width-4, $tag_rect->height-4); # ATTRIBUTES my $attr_rect = Gtk2::Gdk::Rectangle->new($x+2, $y+24, $fullb_ +w-4, int(($fullb_h-32)/2)); $area->draw_rectangle($gc, 0, $attr_rect->x, $attr_rect->y, $a +ttr_rect->width, $attr_rect->height); $self->draw_text($gc, $self->make_text_from_attributes($xmlthi +nk_center, "\n"), $attr_rect->x+2, $attr_rect->y+2, $attr_rect->width +-4, $attr_rect->height-4); # CDATA my $cdata_rect = Gtk2::Gdk::Rectangle->new($x+2, $y+24+int(($f +ullb_h-32)/2)+2, $fullb_w-4, int(($fullb_h-23)/2)); $area->draw_rectangle($gc, 0, $cdata_rect->x, $cdata_rect->y, +$cdata_rect->width, $cdata_rect->height); $self->draw_text($gc, $self->make_text_from_cdata($xmlthink_ce +nter), $cdata_rect->x+2, $cdata_rect->y+2, $cdata_rect->width-4, $cda +ta_rect->height-4); } sub make_text_from_attributes { my ($draw, $element, $sep) = @_; my $str = ''; my $nmap = $element->getAttributes(); for my $i (0..($nmap->getLength() - 1)) { my $node = $nmap->item($i); $str .= $node->getNodeName()."=".$node->getNodeValue() +.$sep; } chop($str); return $str; } sub make_text_from_cdata { my ($draw, $element) = @_; my $str = ''; for my $child ($element->getChildNodes()) { if ($child->getNodeType() != XML::DOM::ELEMENT_NODE && $child->getNodeType() != XML::DOM::ATTRIBUTE_NODE) + { my $o = $child->getNodeValue(); $o =~ s/^\s*//; $o =~ s/\s*$//; $str .= $o."\n" if $o; } } return $str; } sub draw_reticencia { my ($draw, $area, $gc, $x, $y) = @_; $area->draw_arc($gc, 0, $x, $y, (RETICENCIAS_WIDTH / 3), RETIC +ENCIAS_HEIGHT, 90*64, 180*64); $area->draw_arc($gc, 0, $x +(2*RETICENCIAS_WIDTH / 3) - 1, $y, + (RETICENCIAS_WIDTH / 3), RETICENCIAS_HEIGHT, 270*64, 180*64); $area->draw_line($gc, $x+(2*RETICENCIAS_WIDTH/6), $y+(RETICENC +IAS_HEIGHT/2), $x+(4*RETICENCIAS_WIDTH/6), $y+(RETICENCIAS_HEIGHT/2)) +; $area->draw_line($gc, $x+(RETICENCIAS_WIDTH/2), $y, $x+(RETICE +NCIAS_WIDTH/2), $y+(RETICENCIAS_HEIGHT)); } sub draw_collapsed_element { my ($draw, $area, $gc, $element, $x, $y) = @_; my $collb_w = $draw->{collapsed_box_width}; my $collb_h = $draw->{collapsed_box_height}; my $tag = $element->getTagName(); my $attr = $draw->make_text_from_attributes($element, ", "); my $cdata = $draw->make_text_from_cdata($element); $draw->{pixmap}->draw_rectangle($gc, 0, $x, $y, $collb_w, $col +lb_h); $draw->draw_text($gc, $tag, $x+2, $y, $collb_w-4, int($collb_h +/3)); $draw->draw_text($gc, $attr, $x+2, $y+int($collb_h/3), $collb_ +w-4, int($collb_h/3)); $draw->draw_text($gc, $cdata, $x+2, $y+2*int($collb_h/3), $col +lb_w-4, int($collb_h/3)); } sub draw_text { my ($draw, $gc, $text, $x, $y, $w, $h) = @_; my $pangoc = $draw->get_pango_context(); my $fontdesc = Gtk2::Pango::FontDescription->from_string("Sans + 10"); my $rect = Gtk2::Gdk::Rectangle->new($x, $y, $w, $h); my $layout = Gtk2::Pango::Layout->new($pangoc); my $clipped = Gtk2::Gdk::GC->new($draw->{pixmap}); $clipped->set_clip_rectangle($rect); $layout->set_font_description($fontdesc); $layout->set_text($text); $draw->{pixmap}->draw_layout($clipped, $x, $y, $layout); } sub draw_line_to_parent { my ($self, $area, $gc, $x, $y) = @_; my $canvas_width = $self->allocation->width; my $canvas_height = $self->allocation->height; $area->draw_line($gc, $x, $y, $x-int($self->{insets}/2), $y); $area->draw_line($gc, $x-int($self->{insets}/2), $y, $x-int($s +elf->{insets}/2), int($canvas_height/2)-int($self->{insets}/2)); $area->draw_line($gc, $x-int($self->{insets}/2), int($canvas_h +eight/2)-int($self->{insets}/2), $x-$self->{insets}, int($canvas_heig +ht/2)-int($self->{insets}/2)); } sub scroll_event { my ($self, $event) = @_; my $x = $event->x; my $y = $event->y; my $dir = $event->direction; my ($col, $row) = $self->x_y_to_col_row($x, $y); if ($col > 0) { my $other = $dir eq 'up'?-1:1; if (exists $self->{matrix}{$col}{$other} && $self->{ma +trix}{$col}{$other}) { $self->{matrix}{$col}{0} = $self->{matrix}{$co +l}{$other}; while (exists $self->{matrix}{$col + 1}) { delete $self->{matrix}{$col + 1}; $col++; } $self->plan_matrix(); } } } sub element_on_col_row { my ($self, $col, $row) = @_; return $self->{matrix}{$col}{$row}; } sub x_y_to_col_row { my ($self, $x, $y) = @_; my ($col, $row); my $canvas_width = $self->allocation->width; my $canvas_height = $self->allocation->height; my $center_x = int($canvas_width/2); my $center_y = int($canvas_height/2); my $fullb_w = $self->{full_box_width}; my $fullb_h = $self->{full_box_height}; my $collb_w = $self->{collapsed_box_width}; my $collb_h = $self->{collapsed_box_height}; my $insets = $self->{insets}; if ($x > ($center_x + int($fullb_w/2))) { my $rel_x = $x - ($center_x + int($fullb_w/2)); $col = int($rel_x/($collb_w+$insets)) + 1; my $rel_y = $y - $center_y; $row = ceil($rel_y/($collb_h+$insets))+0; } elsif ($x < ($center_x - int($fullb_w/2))) { my $rel_x = $x - ($center_x - int($fullb_w/2)); $col = int($rel_x/($collb_w+$insets)) - 1; my $rel_y = $y - $center_y; $row = ceil($rel_y/($collb_h+$insets)); } else { $col = 0; if ($y > ($center_y + int($fullb_h/2))) { my $rel_y = $y - ($center_y + int($fullb_h/2)) +; $row = int($rel_y/($collb_h+$insets)) + 1; } elsif ($y < ($center_y - int($fullb_h/2))) { my $rel_y = $y - ($center_y - int($fullb_h/2) +- int($insets/2)); $row = int($rel_y/($collb_h+$insets)) - 1; } } return ($col, $row); } sub button_press_event { my ($draw, $event) = @_; my $x = $event->x; my $y = $event->y; my $button = $event->button; my ($col, $row) = $draw->x_y_to_col_row($x, $y); $draw->click_col_row($button, $col, $row); } sub click_col_row { my ($self, $button, $col, $row) = @_; $col ||= 0; $row ||= 0; if (exists $self->{matrix}{$col}{$row} and $self->{matrix}{$co +l}{$row}) { my $obj = $self->{matrix}{$col}{$row}; if ($button == 1) { $self->{matrix} = {}; $self->{matrix}{0}{0} = $obj; $self->plan_matrix(); } } } 1;

Trial Program (with the patch from zentara's comment below):

#!/usr/bin/perl use strict; use warnings; use XML::DOM; use Gtk2 qw(-init); use XMLNavigator; my $mainwindow = Gtk2::Window->new("toplevel"); my $hbox = Gtk2::HBox->new(); my $parser = XML::DOM::Parser->new(); my $nav = XMLNavigator->new(domdocument => $parser->parsefile($ARGV[0] +)); $hbox->add($nav); $mainwindow->add($hbox); $mainwindow->signal_connect( destroy => sub { Gtk2->main_quit; } ); $mainwindow->set_size_request(500, 500); $mainwindow->show_all(); Gtk2->main();
daniel

In reply to XML Navigator by ruoso

Title:
Use:  <p> text here (a paragraph) </p>
and:  <code> code here </code>
to format your post, it's "PerlMonks-approved HTML":



  • Posts are HTML formatted. Put <p> </p> tags around your paragraphs. Put <code> </code> tags around your code and data!
  • Titles consisting of a single word are discouraged, and in most cases are disallowed outright.
  • Read Where should I post X? if you're not absolutely sure you're posting in the right place.
  • Please read these before you post! —
  • Posts may use any of the Perl Monks Approved HTML tags:
    a, abbr, b, big, blockquote, br, caption, center, col, colgroup, dd, del, details, div, dl, dt, em, font, h1, h2, h3, h4, h5, h6, hr, i, ins, li, ol, p, pre, readmore, small, span, spoiler, strike, strong, sub, summary, sup, table, tbody, td, tfoot, th, thead, tr, tt, u, ul, wbr
  • You may need to use entities for some characters, as follows. (Exception: Within code tags, you can put the characters literally.)
            For:     Use:
    & &amp;
    < &lt;
    > &gt;
    [ &#91;
    ] &#93;
  • Link using PerlMonks shortcuts! What shortcuts can I use for linking?
  • See Writeup Formatting Tips and other pages linked from there for more info.