使用CSV数据更新XML的Perl脚本报错及功能实现问题
Fixing Your Perl Script for XML Template Replacement
Let's break down and fix the issues you're facing:
Key Problems in the Original Script
- The
namevalue isn't a separate tag—it's an attribute of the root<ns0:networkdevice>element, solook_down('_tag' => 'name')returnsundef, causing thepush_contenterror. - Your code was trying to output the original unmodified XML tree (
$tree) instead of each processed template for every CSV row. - The
ipaddresslookup wasn't targeting the correct nested element (it's buried inside<NetworkDeviceIP>under<NetworkDeviceIPList>).
Modified Working Script
#!/usr/bin/perl use warnings; use strict; use Text::CSV; use XML::TreeBuilder; my $xmlbase = 'template.xml'; my $paramsfile = 'input.csv'; # Map CSV columns to their XML target: attribute or nested tag my @field_map = ( { type => 'attr', target_tag => 'networkdevice', attr_name => 'name' }, { type => 'tag', target_tag => 'ipaddress', parent_path => ['NetworkDeviceIPList', 'NetworkDeviceIP'] } ); my $csv = Text::CSV->new({ binary => 1 }) or die "Cannot use CSV: " . Text::CSV->error_diag(); open my $fh, "<:encoding(utf8)", $paramsfile or die "$paramsfile: $!"; # Uncomment below if your Input.csv has a header row (e.g., Name, IP Address) # $csv->getline($fh); while ( my $row = $csv->getline($fh) ) { # Load fresh template for each device to avoid cross-row contamination my $contree = XML::TreeBuilder->new(); $contree->parsefile($xmlbase); my $index = 0; foreach my $field (@field_map) { my $value = $row->[$index++]; next unless defined $value; if ($field->{type} eq 'attr') { # Update root element's name attribute my $root = $contree->look_down('_tag' => $field->{target_tag}); $root->attr($field->{attr_name}, $value) if $root; } elsif ($field->{type} eq 'tag') { # Traverse nested path to find the parent of ipaddress my $parent_node = $contree; foreach my $path_part (@{$field->{parent_path}}) { $parent_node = $parent_node->look_down('_tag' => $path_part); last unless $parent_node; } # Replace existing ipaddress content with CSV value if ($parent_node) { my $ip_tag = $parent_node->look_down('_tag' => $field->{target_tag}); $ip_tag->replace_content($value) if $ip_tag; } } } # Output the processed XML for this device (or save to a file) print '<?xml version="1.0" encoding="UTF-8"?>' . "\n"; print $contree->as_XML() . "\n\n"; # Clean up to prevent memory leaks $contree->delete(); } close $fh;
What Changed & Why
- Field Mapping Structure: We use an array of hashes to clearly define if each CSV field targets an XML attribute or a nested tag. This makes the script easier to adjust if your XML structure changes later.
- Handling the
nameAttribute: We directly target the root<ns0:networkdevice>element (XML::TreeBuilder automatically strips namespace prefixes from tag names) and use theattr()method to set itsnameproperty—this fixes thepush_contenterror entirely. - Nested
ipaddressUpdate: We traverse the XML hierarchy step-by-step (NetworkDeviceIPList→NetworkDeviceIP) to find the correct parent of theipaddresstag, then usereplace_content()to overwrite its default value (instead of appending to it withpush_content). - Per-Row Template Freshness: We load a new template instance for every CSV row to ensure changes from one device don't leak into the next.
- Memory Management: Added
$contree->delete()after processing each device to free up memory, which is helpful if you have a large CSV file.
Optional Enhancements
- If your
Input.csvincludes a header row, uncomment the line that skips it to avoid processing the header as a device entry. - Instead of printing to the console, you could save each processed XML to a unique file (e.g.,
LAB-1.xml) by adding file-handling code inside the loop.
内容的提问来源于stack exchange,提问作者StayCalm
相关产品推荐
相关产品推荐

