Your example shows that "sub-records" can have odd number of elements and therefore can't be "composed of KEY, VALUE pairs". Obviously, some sub-records are arrays, not hashes. With such loose brief, there's room for interpretation whether to parse sub-record into array or hash. My attempt below assumes "keep arrays for odd number of elements or if unapproved keys were encountered". (E.g. for "us-east-1 us-west-1" sub-record, are they key-value pair or 2-element list?) Obviously, these rules can be adjusted, but idea was to let Perl parse input as Perl source, with only minimal text pre-processing, and always assume arrays. Afterwards, promote some arrays to hashes if they pass rules mentioned above.
use strict;
use warnings;
use Data::Dumper;
my $s = <<'END';
sys ecm cloud-provider /Common/aws-ec2 {
description "The aws-ec2 parameters"
property-template {
account { }
availability-zone {
valid-values { a b c d }
}
instance-type {
valid-values { t2.micro t2.small t2.medium }
}
region {
valid-values { us-east-1 us-west-1 }
}
}
}
END
my %valid_keys = map { $_, 1 } qw/
description
property-template
account
availability-zone
instance-type
valid-values
region
/;
$s =~ s/
(?|
"([^"{}]+)"
|
(?:^|(?<=\s))
([^\s{}]+)
(?:$|(?=\s))
)
/"$1"/xg;
$s =~ s/("|})\s\K/,/g;
$s =~ tr/{}/[]/;
print Dumper fix_aref([ eval $s ]);
sub fix_aref {
my $aref = shift;
ref and $_ = fix_aref($_) for @$aref;
return $aref unless $#$aref % 2;
for ( 0 .. $#$aref ) {
next if $_ % 2;
return $aref
unless exists $valid_keys{ ${$aref}[$_] }
}
return +{ @$aref }
}
Output:
$VAR1 = [
'sys',
'ecm',
'cloud-provider',
'/Common/aws-ec2',
{
'property-template' => {
'region' => {
'valid-values' => [
+ 'us-east-1',
+ 'us-west-1'
]
},
'availability-zone' => {
'valid-v
+alues' => [
+ 'a',
+ 'b',
+ 'c',
+ 'd'
+ ]
},
'instance-type' => {
'valid-value
+s' => [
+ 't2.micro',
+ 't2.small',
+ 't2.medium'
+ ]
},
'account' => {}
},
'description' => 'The aws-ec2 parameters'
}
];