0

Been banging my head against the wall trying to figure this out. Just trying to put together a script that will eventually link together various components of these game files (like for a wiki). I'm sure this is a simple matter of not doing the deferencing right but I can't figure it out for the life of me.

Here's a sample of the XML:

 <?xml version="1.0" encoding="UTF-8"?>
<items>
        <!-- ???????????????????????????????????????????????????????????????????????????????  -->
    <!-- ?????????? Die Till Day 7 ?????????? Guns (Gun Parts) ?????????????????????????  -->
    <!-- ???????????????????????????????????????????????????????????????????????????????  -->
        <item id="1" name="pistol">
        <property name="Meshfile" value="Items/Weapons/Ranged/Pistol/PistolPrefab" />
        <property name="Material" value="metal" />
        <property name="HoldType" value="1" />
        <property name="Stacknumber" value="1" />
        <property name="RepairTools" value="repairKit,repairKit2ndGeneration,repairKit3rdGeneration" />
        <property name="Degradation" value="175" param1="false" />
        <property name="DegradationBreaksAfter" value="false" />
        <property name="SoundJammed" value="Weapons/weapon_jam" />
                <property name="Attachments" value="flashlight02" />
        <property class="Parts">
            <property name="Stock" value="pistol_grip" />
            <property name="Receiver" value="pistol_receiver" />
            <property name="Pump" value="pistol_parts" />
            <property name="Barrel" value="pistol_barrel" />
        </property>
        <property class="Action0">
            <property name="Class" value="Ranged" />
            <property name="Delay" value="0.15" />
            <property name="Range" value="120" />
            <property name="DamageBlock" value="1" />
            <property name="Magazine_size" value="15" />
            <property name="Magazine_items" value="9mmBullet, incendiary9mmBullet" />
            <property name="Reload_time" value="2" />
            <property name="Bullet_icon" value="pistol" />
            <property name="Sound_start" value="Weapons/Ranged/Pistol/pistol_fire" />
            <property name="Sound_repeat" value="" />
            <property name="Sound_end" value="" />
            <property name="Sound_empty" value="Weapons/weapon_empty" />
            <property name="Sound_reload" value="Weapons/Ranged/Pistol/pistol_reload" />
            <property name="Particles_muzzle_fire" value="nozzleflash" />
            <property name="Particles_muzzle_smoke" value="nozzlesmoke" />
            <property name="DamageBonus.head" value="6" />
            <property name="DamageBonus.glass" value="25" />
        </property>
        <property class="Action1">
            <property name="Class" value="Zoom" />
            <property name="Zoom_max_out" value="35" />
            <property name="Zoom_max_in" value="35" />
        </property>
                <property name="LightSource" value="lightSource" />
                <property name="ActivateObject" value="Attachments/flashlight/lightSource" /> 
                <property name="AttachmentFlashlight" value="flashlight02" />
        <property name="Group" value="Ammo/Weapons" />
        <property class="Preview">
            <property name="Zoom" value="14" />
            <property name="Pos" value="0.1,-0.1" />
            <property name="Rot" value="0,-45,0" />
        </property>
    </item>
</items>

Here is the actual program

    #!/usr/local/bin/perl
use warnings;
#use strict;
use XML::Simple qw(:strict);
use Data::Dumper;

my $xml = new XML::Simple;

my $data = $xml->XMLin(undef, KeyAttr => { item => 'name' }, ForceArray => ['item',  'property', 'property']);
#my $data = XMLin ($xml,  forcearray => [ qw (item property property) ], keyattr = +> [] );



#print Dumper($data);

#my @itemlist = @{$data->{name}};
#print Dumper( \@itemlist );

my $items = $data->{item};

for my $item_name (keys %$items) {
my $item = $items->{$item_name};
my $item_id = $item->{id};
my $props = $item->{property};

print "Name:  " . $item_name . " ID:  " . $item_id;

for my $prop (@$props)
{
my $prop_name = $prop->{name};
my $prop_value = $prop->{value};

print $prop_name ":  " . $prop_value . "\n";
}

}

Really just looking for a few things like how do I access the actual item name (in this case pistol) from a foreach and how would i get down to a property or the property of a property. I'm pretty sure I'm just screwing up what's a hash vs an array or not setting items right but I haven't found any examples that are close enough to my xml data to figure it out.

(Data Dumper gives me this)

$VAR1 = {
          'item' => {
                    'pistol' => {
                                'id' => '1',
                                'property' => [
                                              {
                                                'value' => 'Items/Weapons/Ranged/Pistol/PistolPrefab',
                                                'name' => 'Meshfile'
                                              },
                                              {
                                                'value' => 'metal',
                                                'name' => 'Material'
                                              },
                                              {
                                                'value' => '1',
                                                'name' => 'HoldType'
                                              },
                                              {
                                                'value' => '1',
                                                'name' => 'Stacknumber'
                                              },
                                              {
                                                'value' => 'repairKit,repairKit2ndGeneration,repairKit3rdGeneration',
                                                'name' => 'RepairTools'
                                              },
                                              {
                                                'value' => '175',
                                                'name' => 'Degradation',
                                                'param1' => 'false'
                                              },
                                                     etc

Errors:

No more errors!

Calbrenar
  • 31
  • 7
  • 1
    See also: [Why is XML::Simple “Discouraged”?](http://stackoverflow.com/q/33267765/589924) – ikegami Nov 11 '15 at 18:25
  • Give us some sample XML, and we can give you a better answer. Being able to use XML based constructs mean the code is way less of a headache. – Sobrique Nov 11 '15 at 19:28

2 Answers2

1
my $items = $data->{item};
for my $item_name (keys %$items) {
   my $item = $items->{$item_name};
   my $item_id = $item->{id};
   my $props = $item->{property};
   for my $prop (@$props) {
      my $prop_name = $prop->{name};
      my $prop_value = $prop->{value};
      ...
   }
}

You've since updated to show that some properties look like:

{
    'name' => '...'
    'value' => '...',
}

and some look like

{
    'class' => '...',
    'property' => [ ... ],
}

but your code only handles the first kind. You probably want something like if ($prop->{class}).


print $prop_name ":  " . $prop_value . "\n";

treats $prop_name as a file handle. It should be

print $prop_name . ":  " . $prop_value . "\n";
ikegami
  • 367,544
  • 15
  • 269
  • 518
  • Thank you so much. I actually read through several of your previous answers while trying to figure this out. I'm sure I can backtrack what you did to figure out how to get to the id or a property value. Many thanks! – Calbrenar Nov 11 '15 at 19:16
  • I added a simple print on $name $id and $props. $name works but I get use of uninitialized value for the lines using id and props and for the print statement for id and prompts. I assumed props would be another hash or array reference right with potentially another array/hash inside it? – Calbrenar Nov 11 '15 at 20:44
  • You appear to have changed something seeing as what I had posted didn't even compile. I removed the extraneous `$`, and now it runs fine (tested). – ikegami Nov 11 '15 at 20:59
  • This is what I put in -- http://pastebin.com/yj9kAPM4D The first two work fine the second two give unitialized values and a bunch of open filehandle errors http://pastebin.com/BrviDSy7 Thanks again for your help. What's the best thing to look up for a tutorial on this? It seems like it's mixed across hashes and references. – Calbrenar Nov 11 '15 at 21:08
  • `skullCapBrown` doesn't exist anywhere in the XML, so you couldn't possibly have gotten that output. – ikegami Nov 11 '15 at 21:12
  • The xml file is too big to put on pastebin so I only had one of the values there's several hundred I think. Here's the full file https://www.dropbox.com/s/fycg9f0sd9tbtzh/items.xml?dl=0 – Calbrenar Nov 11 '15 at 21:14
  • You shouldn't be using external sites at all. Your question should be self-contained. If your question is wrong, fix it.You have more than enough space to provide a runnable demonstration of the problem. – ikegami Nov 11 '15 at 21:15
  • Sorry I thought it'd be too much to include. I ran the code on just the exceprt from pastebin and I get the sameresults (But different name/id of course) I'm trying to update the question to include the XML but it's filtering it all out. – Calbrenar Nov 11 '15 at 21:36
0

This doesn't directly answer your question, but I'm hoping to show an alternative approach - I'm a firm believer that XML::Simple... isn't. It makes your code far more complex than it needs to be. So a few examples on parsing your XML:

#!/usr/bin/env perl
use strict;
use warnings;
use XML::Twig;

#parse the file    
my $twig = XML::Twig -> new(); 
   $twig -> parsefile ( 'sample.xml' );

#iterate 'item' elements - note '//' means anywhere in document. 
#you can use './' to denote only things under the current node. 
foreach my $item ( $twig -> get_xpath ('//item') ) {

    #item attributes:
    print "ID: ", $item -> att('id'),"\n";
    print "name: ", $item -> att('name'),"\n";

    #subelement with a specific attribute name:
    print "Material: ", $item -> get_xpath("./property[\@name='Material']", 0) -> att('value'),"\n";

    #grab a nested property:
    print "Action0 Range:";
    #nb - errors on item 2, because it doesn't exist. This is illustrative. 
    print $item -> get_xpath(q{./property[@class='Action0']/property[@name='Range']},0) -> att('value'),"\n";


    #iterate children:
    print "Properties:\n";
    #note - children only iterates beneath current node. 
    foreach my $property ( $item -> children('property') ) {
        print $property -> att('name'),"\n" if defined $property->att('name');
    }

}
Sobrique
  • 52,974
  • 7
  • 60
  • 101
  • I saw Twig and really wanted to use it but unfortunately the server host I have for my site doesn't have Twig or Lib:XML so been stuck w/ XML simple. I'm going to open a ticket with them to try to install it. Not sure how long it will take but will definitely try your code once they put it in. – Calbrenar Nov 11 '15 at 21:43
  • It's often in default repositories. I've got it on `yum install` on my Linux builds. But I really think it's worth doing - `XML::Simple` ... actually isn't _much_ better than using [regular expressions to parse](http://stackoverflow.com/questions/1732348/regex-match-open-tags-except-xhtml-self-contained-tags/1732454#1732454) – Sobrique Nov 11 '15 at 21:48
  • I tried this and it's much easier to read what's going on. Thank you for showing this alternate method – Calbrenar Nov 12 '15 at 14:10