line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
############################################################################# |
2
|
|
|
|
|
|
|
# Parse text definition into a Graph::Easy object |
3
|
|
|
|
|
|
|
# |
4
|
|
|
|
|
|
|
############################################################################# |
5
|
|
|
|
|
|
|
|
6
|
|
|
|
|
|
|
package Graph::Easy::Parser; |
7
|
|
|
|
|
|
|
|
8
|
19
|
|
|
19
|
|
84560
|
use Graph::Easy; |
|
19
|
|
|
|
|
31
|
|
|
19
|
|
|
|
|
622
|
|
9
|
|
|
|
|
|
|
|
10
|
|
|
|
|
|
|
$VERSION = '0.76'; |
11
|
19
|
|
|
19
|
|
75
|
use Graph::Easy::Base; |
|
19
|
|
|
|
|
20
|
|
|
19
|
|
|
|
|
468
|
|
12
|
|
|
|
|
|
|
@ISA = qw/Graph::Easy::Base/; |
13
|
19
|
|
|
19
|
|
68
|
use Scalar::Util qw/weaken/; |
|
19
|
|
|
|
|
16
|
|
|
19
|
|
|
|
|
841
|
|
14
|
|
|
|
|
|
|
|
15
|
19
|
|
|
19
|
|
201
|
use strict; |
|
19
|
|
|
|
|
811
|
|
|
19
|
|
|
|
|
363
|
|
16
|
19
|
|
|
19
|
|
60
|
use warnings; |
|
19
|
|
|
|
|
15
|
|
|
19
|
|
|
|
|
475
|
|
17
|
19
|
|
|
19
|
|
54
|
use constant NO_MULTIPLES => 1; |
|
19
|
|
|
|
|
17
|
|
|
19
|
|
|
|
|
1002
|
|
18
|
|
|
|
|
|
|
|
19
|
19
|
|
|
19
|
|
74
|
use Graph::Easy::Util qw(ord_values); |
|
19
|
|
|
|
|
19
|
|
|
19
|
|
|
|
|
97946
|
|
20
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
sub _init |
22
|
|
|
|
|
|
|
{ |
23
|
81
|
|
|
81
|
|
95
|
my ($self,$args) = @_; |
24
|
|
|
|
|
|
|
|
25
|
81
|
|
|
|
|
182
|
$self->{error} = ''; |
26
|
81
|
|
|
|
|
94
|
$self->{debug} = 0; |
27
|
81
|
|
|
|
|
103
|
$self->{fatal_errors} = 1; |
28
|
|
|
|
|
|
|
|
29
|
81
|
|
|
|
|
272
|
foreach my $k (sort keys %$args) |
30
|
|
|
|
|
|
|
{ |
31
|
117
|
50
|
|
|
|
356
|
if ($k !~ /^(debug|fatal_errors)\z/) |
32
|
|
|
|
|
|
|
{ |
33
|
0
|
|
|
|
|
0
|
require Carp; |
34
|
0
|
|
|
|
|
0
|
my $class = ref($self); |
35
|
0
|
|
|
|
|
0
|
Carp::confess ("Invalid argument '$k' passed to $class" . '->new()'); |
36
|
|
|
|
|
|
|
} |
37
|
117
|
|
|
|
|
139
|
$self->{$k} = $args->{$k}; |
38
|
|
|
|
|
|
|
} |
39
|
|
|
|
|
|
|
|
40
|
|
|
|
|
|
|
# what to replace the matched text with |
41
|
81
|
|
|
|
|
158
|
$self->{replace} = ''; |
42
|
81
|
|
|
|
|
102
|
$self->{attr_sep} = ':'; |
43
|
|
|
|
|
|
|
# An optional regexp to remove parts of an autosplit label, used by Graphviz |
44
|
|
|
|
|
|
|
# to remove " ": |
45
|
81
|
|
|
|
|
100
|
$self->{_qr_part_clean} = undef; |
46
|
|
|
|
|
|
|
|
47
|
|
|
|
|
|
|
# setup default class names for generated objects |
48
|
|
|
|
|
|
|
$self->{use_class} = { |
49
|
81
|
|
|
|
|
281
|
edge => 'Graph::Easy::Edge', |
50
|
|
|
|
|
|
|
group => 'Graph::Easy::Group', |
51
|
|
|
|
|
|
|
graph => 'Graph::Easy', |
52
|
|
|
|
|
|
|
node => 'Graph::Easy::Node', |
53
|
|
|
|
|
|
|
}; |
54
|
|
|
|
|
|
|
|
55
|
81
|
|
|
|
|
160
|
$self; |
56
|
|
|
|
|
|
|
} |
57
|
|
|
|
|
|
|
|
58
|
|
|
|
|
|
|
sub reset |
59
|
|
|
|
|
|
|
{ |
60
|
|
|
|
|
|
|
# reset the status of the parser, clear errors etc. |
61
|
802
|
|
|
802
|
1
|
64245
|
my $self = shift; |
62
|
|
|
|
|
|
|
|
63
|
802
|
|
|
|
|
1129
|
$self->{error} = ''; |
64
|
802
|
|
|
|
|
859
|
$self->{anon_id} = 0; |
65
|
802
|
|
|
|
|
882
|
$self->{cluster_id} = ''; # each cluster gets a unique ID |
66
|
802
|
|
|
|
|
764
|
$self->{line_nr} = -1; |
67
|
802
|
|
|
|
|
1027
|
$self->{match_stack} = []; # patterns and their handlers |
68
|
|
|
|
|
|
|
|
69
|
802
|
|
|
|
|
17947
|
$self->{clusters} = {}; # cluster names we already created |
70
|
|
|
|
|
|
|
|
71
|
802
|
|
|
|
|
1898
|
Graph::Easy::Base::_reset_id(); # start with the same set of IDs |
72
|
|
|
|
|
|
|
|
73
|
|
|
|
|
|
|
# After "[ 1 ] -> [ 2 ]" we push "2" on the stack and when we encounter |
74
|
|
|
|
|
|
|
# " -> [ 3 ]" treat the stack as a node-list left of "3". |
75
|
|
|
|
|
|
|
# In addition, for " [ 1 ], [ 2 ] => [ 3 ]", the stack will contain |
76
|
|
|
|
|
|
|
# "1" and "2" when we encounter "3". |
77
|
802
|
|
|
|
|
868
|
$self->{stack} = []; |
78
|
|
|
|
|
|
|
|
79
|
802
|
|
|
|
|
990
|
$self->{group_stack} = []; # all the (nested) groups we are currently in |
80
|
802
|
|
|
|
|
918
|
$self->{left_stack} = []; # stack for the left side for "[]->[],[],..." |
81
|
802
|
|
|
|
|
932
|
$self->{left_edge} = undef; # for -> [A], [B] continuations |
82
|
|
|
|
|
|
|
|
83
|
802
|
|
|
|
|
2653
|
Graph::Easy->_drop_special_attributes(); |
84
|
|
|
|
|
|
|
|
85
|
|
|
|
|
|
|
$self->{_graph} = $self->{use_class}->{graph}->new( { |
86
|
|
|
|
|
|
|
debug => $self->{debug}, |
87
|
|
|
|
|
|
|
strict => 0, |
88
|
|
|
|
|
|
|
fatal_errors => $self->{fatal_errors}, |
89
|
802
|
|
|
|
|
3897
|
} ); |
90
|
|
|
|
|
|
|
|
91
|
802
|
|
|
|
|
1878
|
$self; |
92
|
|
|
|
|
|
|
} |
93
|
|
|
|
|
|
|
|
94
|
|
|
|
|
|
|
sub from_file |
95
|
|
|
|
|
|
|
{ |
96
|
|
|
|
|
|
|
# read in entire file and call from_text() on the contents |
97
|
2
|
|
|
2
|
1
|
14
|
my ($self,$file) = @_; |
98
|
|
|
|
|
|
|
|
99
|
2
|
50
|
|
|
|
27
|
$self = $self->new() unless ref $self; |
100
|
|
|
|
|
|
|
|
101
|
2
|
|
|
|
|
3
|
my $doc; |
102
|
2
|
|
|
|
|
8
|
local $/ = undef; # slurp mode |
103
|
|
|
|
|
|
|
# if given a reference, assume it is a glob, or something like that |
104
|
2
|
50
|
|
|
|
7
|
if (ref($file)) |
105
|
|
|
|
|
|
|
{ |
106
|
0
|
0
|
|
|
|
0
|
binmode $file, ':utf8' or die ("binmode '$file', ':utf8' failed: $!"); |
107
|
0
|
|
|
|
|
0
|
$doc = <$file>; |
108
|
|
|
|
|
|
|
} |
109
|
|
|
|
|
|
|
else |
110
|
|
|
|
|
|
|
{ |
111
|
2
|
50
|
|
|
|
84
|
open my $PARSER_FILE, $file or die (ref($self).": Cannot read $file: $!"); |
112
|
2
|
50
|
|
|
|
15
|
binmode $PARSER_FILE, ':utf8' or die ("binmode '$file', ':utf8' failed: $!"); |
113
|
2
|
|
|
|
|
35
|
$doc = <$PARSER_FILE>; # read entire file |
114
|
2
|
|
|
|
|
16
|
close $PARSER_FILE; |
115
|
|
|
|
|
|
|
} |
116
|
|
|
|
|
|
|
|
117
|
2
|
|
|
|
|
8
|
$self->from_text($doc); |
118
|
|
|
|
|
|
|
} |
119
|
|
|
|
|
|
|
|
120
|
|
|
|
|
|
|
sub use_class |
121
|
|
|
|
|
|
|
{ |
122
|
|
|
|
|
|
|
# use the provided class for generating objects of the type $object |
123
|
4
|
|
|
4
|
1
|
13
|
my ($self, $object, $class) = @_; |
124
|
|
|
|
|
|
|
|
125
|
4
|
50
|
|
|
|
17
|
$self->_croak("Expected one of node, edge, group or graph, but got $object") |
126
|
|
|
|
|
|
|
unless $object =~ /^(node|group|graph|edge)\z/; |
127
|
|
|
|
|
|
|
|
128
|
4
|
|
|
|
|
5
|
$self->{use_class}->{$object} = $class; |
129
|
|
|
|
|
|
|
|
130
|
4
|
|
|
|
|
4
|
$self; |
131
|
|
|
|
|
|
|
} |
132
|
|
|
|
|
|
|
|
133
|
|
|
|
|
|
|
sub _register_handler |
134
|
|
|
|
|
|
|
{ |
135
|
|
|
|
|
|
|
# register a pattern and a handler for it |
136
|
4341
|
|
|
4341
|
|
3363
|
my $self = shift; |
137
|
|
|
|
|
|
|
|
138
|
4341
|
|
|
|
|
2697
|
push @{$self->{match_stack}}, [ @_ ]; |
|
4341
|
|
|
|
|
6085
|
|
139
|
|
|
|
|
|
|
|
140
|
4341
|
|
|
|
|
5280
|
$self; |
141
|
|
|
|
|
|
|
} |
142
|
|
|
|
|
|
|
|
143
|
|
|
|
|
|
|
sub _register_attribute_handler |
144
|
|
|
|
|
|
|
{ |
145
|
|
|
|
|
|
|
# register a handler for attributes like "{ color: red; }" |
146
|
541
|
|
|
541
|
|
626
|
my ($self, $qr_attr, $target) = @_; |
147
|
|
|
|
|
|
|
|
148
|
|
|
|
|
|
|
# $object is either undef (for Graph::Easy, meaning "node", or "parent" for Graphviz) |
149
|
|
|
|
|
|
|
|
150
|
|
|
|
|
|
|
# { attributes } |
151
|
|
|
|
|
|
|
$self->_register_handler( qr/^$qr_attr/, |
152
|
|
|
|
|
|
|
sub |
153
|
|
|
|
|
|
|
{ |
154
|
41
|
|
|
41
|
|
39
|
my $self = shift; |
155
|
|
|
|
|
|
|
# This happens in the case of "[ Test ]\n { ... }", the node is consumed |
156
|
|
|
|
|
|
|
# first, and the attributes are left over: |
157
|
|
|
|
|
|
|
|
158
|
41
|
100
|
|
|
|
41
|
my $stack = $self->{stack}; $stack = $self->{group_stack} if @{$self->{stack}} == 0; |
|
41
|
|
|
|
|
29
|
|
|
41
|
|
|
|
|
97
|
|
159
|
|
|
|
|
|
|
|
160
|
41
|
|
|
|
|
38
|
my $object = $target; |
161
|
41
|
100
|
66
|
|
|
131
|
if ($target && $target eq 'parent') |
162
|
|
|
|
|
|
|
{ |
163
|
|
|
|
|
|
|
# for Graphviz, stray attributes always apply to the parent |
164
|
33
|
|
|
|
|
31
|
$stack = $self->{group_stack}; |
165
|
|
|
|
|
|
|
|
166
|
33
|
50
|
|
|
|
69
|
$object = $stack->[-1] if ref $stack; |
167
|
33
|
100
|
|
|
|
51
|
if (!defined $object) |
168
|
|
|
|
|
|
|
{ |
169
|
|
|
|
|
|
|
# try the scope stack next: |
170
|
22
|
|
|
|
|
20
|
$stack = $self->{scope_stack}; |
171
|
22
|
|
|
|
|
26
|
$object = $self->{_graph}; |
172
|
22
|
50
|
33
|
|
|
80
|
if (!$stack || @$stack <= 1) |
173
|
|
|
|
|
|
|
{ |
174
|
22
|
|
|
|
|
20
|
$object = $self->{_graph}; |
175
|
22
|
|
|
|
|
35
|
$stack = [ $self->{_graph} ]; |
176
|
|
|
|
|
|
|
} |
177
|
|
|
|
|
|
|
} |
178
|
|
|
|
|
|
|
} |
179
|
41
|
|
50
|
|
|
159
|
my ($a, $max_idx) = $self->_parse_attributes($1||'', $object); |
180
|
41
|
50
|
|
|
|
85
|
return undef if $self->{error}; # wrong attributes or empty stack? |
181
|
|
|
|
|
|
|
|
182
|
41
|
50
|
|
|
|
79
|
if (ref($stack->[-1]) eq 'HASH') |
183
|
|
|
|
|
|
|
{ |
184
|
|
|
|
|
|
|
# stack is a scope stack |
185
|
|
|
|
|
|
|
# XXX TODO: Find out wether the attribute goes to graph, node or edge |
186
|
0
|
|
|
|
|
0
|
for my $k (sort keys %$a) |
187
|
|
|
|
|
|
|
{ |
188
|
0
|
|
|
|
|
0
|
$stack->[-1]->{graph}->{$k} = $a->{$k}; |
189
|
|
|
|
|
|
|
} |
190
|
0
|
|
|
|
|
0
|
return 1; |
191
|
|
|
|
|
|
|
} |
192
|
|
|
|
|
|
|
|
193
|
|
|
|
|
|
|
print STDERR "max_idx = $max_idx, stack contains ", join (" , ", @$stack),"\n" |
194
|
41
|
50
|
33
|
|
|
82
|
if $self->{debug} && $self->{debug} > 1; |
195
|
41
|
100
|
|
|
|
67
|
if ($max_idx != 1) |
196
|
|
|
|
|
|
|
{ |
197
|
1
|
|
|
|
|
1
|
my $i = 0; |
198
|
1
|
|
|
|
|
2
|
for my $n (@$stack) |
199
|
|
|
|
|
|
|
{ |
200
|
3
|
|
|
|
|
7
|
$n->set_attributes($a, $i++); |
201
|
|
|
|
|
|
|
} |
202
|
|
|
|
|
|
|
} |
203
|
|
|
|
|
|
|
else |
204
|
|
|
|
|
|
|
{ |
205
|
|
|
|
|
|
|
# set attributes on all nodes/groups on stack |
206
|
40
|
|
|
|
|
46
|
for my $n (@$stack) { $n->set_attributes($a); } |
|
42
|
|
|
|
|
114
|
|
207
|
|
|
|
|
|
|
} |
208
|
|
|
|
|
|
|
# This happens in the case of "[ a | b ]\n { ... }", the node is consumed |
209
|
|
|
|
|
|
|
# first, and the attributes are left over. And if we encounter a basename |
210
|
|
|
|
|
|
|
# attribute here, the node-parts will already have been created with the |
211
|
|
|
|
|
|
|
# wrong basename, so correct this: |
212
|
41
|
100
|
|
|
|
83
|
if (defined $a->{basename}) |
213
|
|
|
|
|
|
|
{ |
214
|
2
|
|
|
|
|
3
|
for my $s (@$stack) |
215
|
|
|
|
|
|
|
{ |
216
|
|
|
|
|
|
|
# for every node on the stack that is the primary one |
217
|
6
|
100
|
|
|
|
15
|
$self->_set_new_basename($s, $a->{basename}) if exists $s->{autosplit_parts}; |
218
|
|
|
|
|
|
|
} |
219
|
|
|
|
|
|
|
} |
220
|
41
|
|
|
|
|
71
|
1; |
221
|
541
|
|
|
|
|
19667
|
} ); |
222
|
|
|
|
|
|
|
} |
223
|
|
|
|
|
|
|
|
224
|
|
|
|
|
|
|
sub _register_node_attribute_handler |
225
|
|
|
|
|
|
|
{ |
226
|
|
|
|
|
|
|
# register a handler for attributes like "[ A ] { ... }" |
227
|
324
|
|
|
324
|
|
346
|
my ($self, $qr_node, $qr_oatr) = @_; |
228
|
|
|
|
|
|
|
|
229
|
|
|
|
|
|
|
$self->_register_handler( qr/^$qr_node$qr_oatr/, |
230
|
|
|
|
|
|
|
sub |
231
|
|
|
|
|
|
|
{ |
232
|
729
|
|
|
729
|
|
751
|
my $self = shift; |
233
|
729
|
|
|
|
|
1150
|
my $n1 = $1; |
234
|
729
|
|
100
|
|
|
2878
|
my $a1 = $self->_parse_attributes($2||''); |
235
|
729
|
50
|
|
|
|
1244
|
return undef if $self->{error}; |
236
|
|
|
|
|
|
|
|
237
|
729
|
|
|
|
|
1359
|
$self->{stack} = [ $self->_new_node ($self->{_graph}, $n1, $self->{group_stack}, $a1) ]; |
238
|
|
|
|
|
|
|
|
239
|
|
|
|
|
|
|
# forget left stack |
240
|
729
|
|
|
|
|
843
|
$self->{left_edge} = undef; |
241
|
729
|
|
|
|
|
971
|
$self->{left_stack} = []; |
242
|
729
|
|
|
|
|
1205
|
1; |
243
|
324
|
|
|
|
|
1935
|
} ); |
244
|
|
|
|
|
|
|
} |
245
|
|
|
|
|
|
|
|
246
|
|
|
|
|
|
|
sub _new_group |
247
|
|
|
|
|
|
|
{ |
248
|
|
|
|
|
|
|
# create a new (possible anonymous) group |
249
|
57
|
|
|
57
|
|
73
|
my ($self, $name) = @_; |
250
|
|
|
|
|
|
|
|
251
|
57
|
50
|
|
|
|
101
|
$name = '' unless defined $name; |
252
|
|
|
|
|
|
|
|
253
|
57
|
|
|
|
|
97
|
my $gr = $self->{use_class}->{group}; |
254
|
|
|
|
|
|
|
|
255
|
57
|
|
|
|
|
52
|
my $group; |
256
|
|
|
|
|
|
|
|
257
|
57
|
100
|
|
|
|
109
|
if ($name eq '') |
258
|
|
|
|
|
|
|
{ |
259
|
15
|
50
|
|
|
|
26
|
print STDERR "# Creating new anon group.\n" if $self->{debug}; |
260
|
15
|
|
|
|
|
18
|
$gr .= '::Anon'; |
261
|
15
|
|
|
|
|
50
|
$group = $gr->new(); |
262
|
|
|
|
|
|
|
} |
263
|
|
|
|
|
|
|
else |
264
|
|
|
|
|
|
|
{ |
265
|
42
|
|
|
|
|
84
|
$name = $self->_unquote($name); |
266
|
42
|
50
|
|
|
|
90
|
print STDERR "# Creating new group '$name'.\n" if $self->{debug}; |
267
|
42
|
|
|
|
|
207
|
$group = $gr->new( name => $name ); |
268
|
|
|
|
|
|
|
} |
269
|
|
|
|
|
|
|
|
270
|
57
|
|
|
|
|
176
|
$self->{_graph}->add_group($group); |
271
|
|
|
|
|
|
|
|
272
|
57
|
|
|
|
|
61
|
my $group_stack = $self->{group_stack}; |
273
|
57
|
100
|
|
|
|
104
|
if (@$group_stack > 0) |
274
|
|
|
|
|
|
|
{ |
275
|
2
|
|
|
|
|
8
|
$group->set_attribute('group', $group_stack->[-1]->{name}); |
276
|
|
|
|
|
|
|
} |
277
|
|
|
|
|
|
|
|
278
|
57
|
|
|
|
|
81
|
$group; |
279
|
|
|
|
|
|
|
} |
280
|
|
|
|
|
|
|
|
281
|
|
|
|
|
|
|
sub _add_group_match |
282
|
|
|
|
|
|
|
{ |
283
|
|
|
|
|
|
|
# register two handlers for group start/end |
284
|
324
|
|
|
324
|
|
302
|
my $self = shift; |
285
|
|
|
|
|
|
|
|
286
|
324
|
|
|
|
|
525
|
my $qr_group_start = $self->_match_group_start(); |
287
|
324
|
|
|
|
|
532
|
my $qr_group_end = $self->_match_group_end(); |
288
|
324
|
|
|
|
|
499
|
my $qr_oatr = $self->_match_optional_attributes(); |
289
|
|
|
|
|
|
|
|
290
|
|
|
|
|
|
|
# "( group start [" or empty group like "( Group )" |
291
|
|
|
|
|
|
|
$self->_register_handler( qr/^$qr_group_start/, |
292
|
|
|
|
|
|
|
sub |
293
|
|
|
|
|
|
|
{ |
294
|
44
|
|
|
44
|
|
41
|
my $self = shift; |
295
|
44
|
|
|
|
|
48
|
my $graph = $self->{_graph}; |
296
|
|
|
|
|
|
|
|
297
|
44
|
50
|
|
|
|
79
|
my $end = $2; $end = '' unless defined $end; |
|
44
|
|
|
|
|
74
|
|
298
|
|
|
|
|
|
|
|
299
|
|
|
|
|
|
|
# repair the start of the next node/group |
300
|
44
|
100
|
|
|
|
105
|
$self->{replace} = '[' if $end eq '['; |
301
|
44
|
100
|
|
|
|
69
|
$self->{replace} = '(' if $end eq '('; |
302
|
|
|
|
|
|
|
|
303
|
|
|
|
|
|
|
# create the new group |
304
|
44
|
|
|
|
|
90
|
my $group = $self->_new_group($1); |
305
|
|
|
|
|
|
|
|
306
|
44
|
100
|
|
|
|
78
|
if ($end eq ')') |
307
|
|
|
|
|
|
|
{ |
308
|
|
|
|
|
|
|
# we matched an empty group like "()", or "( group name )" |
309
|
8
|
|
|
|
|
13
|
$self->{stack} = [ $group ]; |
310
|
8
|
50
|
|
|
|
13
|
print STDERR "# Seen end of group '$group->{name}'.\n" if $self->{debug}; |
311
|
|
|
|
|
|
|
} |
312
|
|
|
|
|
|
|
else |
313
|
|
|
|
|
|
|
{ |
314
|
|
|
|
|
|
|
# only put the group on the stack if it is still open |
315
|
36
|
|
|
|
|
43
|
push @{$self->{group_stack}}, $group; |
|
36
|
|
|
|
|
60
|
|
316
|
|
|
|
|
|
|
} |
317
|
|
|
|
|
|
|
|
318
|
44
|
|
|
|
|
52
|
1; |
319
|
324
|
|
|
|
|
1754
|
} ); |
320
|
|
|
|
|
|
|
|
321
|
|
|
|
|
|
|
# ") { }" # group end (with optional attributes) |
322
|
|
|
|
|
|
|
$self->_register_handler( qr/^$qr_group_end$qr_oatr/, |
323
|
|
|
|
|
|
|
sub |
324
|
|
|
|
|
|
|
{ |
325
|
36
|
|
|
36
|
|
41
|
my $self = shift; |
326
|
|
|
|
|
|
|
|
327
|
36
|
|
|
|
|
36
|
my $group = pop @{$self->{group_stack}}; |
|
36
|
|
|
|
|
54
|
|
328
|
36
|
50
|
|
|
|
57
|
return $self->parse_error(0) if !defined $group; |
329
|
|
|
|
|
|
|
|
330
|
36
|
50
|
|
|
|
57
|
print STDERR "# Seen end of group '$group->{name}'.\n" if $self->{debug}; |
331
|
|
|
|
|
|
|
|
332
|
36
|
|
100
|
|
|
154
|
my $a1 = $self->_parse_attributes($1||'', 'group', NO_MULTIPLES); |
333
|
36
|
50
|
|
|
|
70
|
return undef if $self->{error}; |
334
|
|
|
|
|
|
|
|
335
|
36
|
|
|
|
|
102
|
$group->set_attributes($a1); |
336
|
|
|
|
|
|
|
|
337
|
|
|
|
|
|
|
# the new left side is the group itself |
338
|
36
|
|
|
|
|
51
|
$self->{stack} = [ $group ]; |
339
|
36
|
|
|
|
|
65
|
1; |
340
|
324
|
|
|
|
|
1745
|
} ); |
341
|
|
|
|
|
|
|
|
342
|
|
|
|
|
|
|
} |
343
|
|
|
|
|
|
|
|
344
|
|
|
|
|
|
|
sub _build_match_stack |
345
|
|
|
|
|
|
|
{ |
346
|
|
|
|
|
|
|
# put all known patterns and their handlers on the match stack |
347
|
324
|
|
|
324
|
|
356
|
my $self = shift; |
348
|
|
|
|
|
|
|
|
349
|
|
|
|
|
|
|
# regexps for the different parts |
350
|
324
|
|
|
|
|
615
|
my $qr_node = $self->_match_node(); |
351
|
324
|
|
|
|
|
560
|
my $qr_attr = $self->_match_attributes(); |
352
|
324
|
|
|
|
|
525
|
my $qr_oatr = $self->_match_optional_attributes(); |
353
|
324
|
|
|
|
|
626
|
my $qr_edge = $self->_match_edge(); |
354
|
324
|
|
|
|
|
649
|
my $qr_comma = $self->_match_comma(); |
355
|
324
|
|
|
|
|
600
|
my $qr_class = $self->_match_class_selector(); |
356
|
|
|
|
|
|
|
|
357
|
324
|
|
|
|
|
479
|
my $e = $self->{use_class}->{edge}; |
358
|
|
|
|
|
|
|
|
359
|
|
|
|
|
|
|
# node { color: red; } |
360
|
|
|
|
|
|
|
# node.graph { ... } |
361
|
|
|
|
|
|
|
# .foo { ... } |
362
|
|
|
|
|
|
|
# .foo, node, edge.red { ... } |
363
|
|
|
|
|
|
|
$self->_register_handler( qr/^\s*$qr_class$qr_attr/, |
364
|
|
|
|
|
|
|
sub |
365
|
|
|
|
|
|
|
{ |
366
|
98
|
|
|
98
|
|
98
|
my $self = shift; |
367
|
98
|
|
50
|
|
|
407
|
my $class = lc($1 || ''); |
368
|
98
|
|
50
|
|
|
377
|
my $att = $self->_parse_attributes($2 || '', $class, NO_MULTIPLES ); |
369
|
|
|
|
|
|
|
|
370
|
98
|
50
|
|
|
|
196
|
return undef unless defined $att; # error in attributes? |
371
|
|
|
|
|
|
|
|
372
|
98
|
|
|
|
|
120
|
my $graph = $self->{_graph}; |
373
|
98
|
|
|
|
|
288
|
$graph->set_attributes ( $class, $att); |
374
|
|
|
|
|
|
|
|
375
|
|
|
|
|
|
|
# forget stacks |
376
|
98
|
|
|
|
|
122
|
$self->{stack} = []; |
377
|
98
|
|
|
|
|
109
|
$self->{left_edge} = undef; |
378
|
98
|
|
|
|
|
110
|
$self->{left_stack} = []; |
379
|
98
|
|
|
|
|
392
|
1; |
380
|
324
|
|
|
|
|
2614
|
} ); |
381
|
|
|
|
|
|
|
|
382
|
324
|
|
|
|
|
663
|
$self->_add_group_match(); |
383
|
|
|
|
|
|
|
|
384
|
324
|
|
|
|
|
621
|
$self->_register_attribute_handler($qr_attr); |
385
|
324
|
|
|
|
|
575
|
$self->_register_node_attribute_handler($qr_node,$qr_oatr); |
386
|
|
|
|
|
|
|
|
387
|
|
|
|
|
|
|
# , [ Berlin ] { color: red; } |
388
|
|
|
|
|
|
|
$self->_register_handler( qr/^$qr_comma$qr_node$qr_oatr/, |
389
|
|
|
|
|
|
|
sub |
390
|
|
|
|
|
|
|
{ |
391
|
63
|
|
|
63
|
|
70
|
my $self = shift; |
392
|
63
|
|
|
|
|
69
|
my $graph = $self->{_graph}; |
393
|
63
|
|
|
|
|
97
|
my $n1 = $1; |
394
|
63
|
|
100
|
|
|
260
|
my $a1 = $self->_parse_attributes($2||''); |
395
|
63
|
50
|
|
|
|
178
|
return undef if $self->{error}; |
396
|
|
|
|
|
|
|
|
397
|
63
|
|
|
|
|
156
|
push @{$self->{stack}}, |
398
|
63
|
|
|
|
|
60
|
$self->_new_node ($graph, $n1, $self->{group_stack}, $a1, $self->{stack}); |
399
|
|
|
|
|
|
|
|
400
|
63
|
100
|
|
|
|
135
|
if (defined $self->{left_edge}) |
401
|
|
|
|
|
|
|
{ |
402
|
26
|
|
|
|
|
24
|
my ($style, $edge_label, $edge_atr, $edge_bd, $edge_un) = @{$self->{left_edge}}; |
|
26
|
|
|
|
|
44
|
|
403
|
|
|
|
|
|
|
|
404
|
26
|
|
|
|
|
19
|
foreach my $node (@{$self->{left_stack}}) |
|
26
|
|
|
|
|
38
|
|
405
|
|
|
|
|
|
|
{ |
406
|
28
|
|
|
|
|
94
|
my $edge = $e->new( { style => $style, name => $edge_label } ); |
407
|
28
|
|
|
|
|
70
|
$edge->set_attributes($edge_atr); |
408
|
|
|
|
|
|
|
# "<--->": bidirectional |
409
|
28
|
100
|
|
|
|
49
|
$edge->bidirectional(1) if $edge_bd; |
410
|
28
|
50
|
|
|
|
43
|
$edge->undirected(1) if $edge_un; |
411
|
28
|
|
|
|
|
118
|
$graph->add_edge ( $node, $self->{stack}->[-1], $edge ); |
412
|
|
|
|
|
|
|
} |
413
|
|
|
|
|
|
|
} |
414
|
63
|
|
|
|
|
107
|
1; |
415
|
324
|
|
|
|
|
2063
|
} ); |
416
|
|
|
|
|
|
|
|
417
|
|
|
|
|
|
|
# Things like "[ Node ]" will be consumed before, so we do not need a case |
418
|
|
|
|
|
|
|
# for "[ A ] -> [ B ]": |
419
|
|
|
|
|
|
|
# node chain continued like "-> { ... } [ Kassel ] { ... }" |
420
|
|
|
|
|
|
|
$self->_register_handler( qr/^$qr_edge$qr_oatr$qr_node$qr_oatr/, |
421
|
|
|
|
|
|
|
sub |
422
|
|
|
|
|
|
|
{ |
423
|
780
|
|
|
780
|
|
745
|
my $self = shift; |
424
|
|
|
|
|
|
|
|
425
|
780
|
50
|
|
|
|
598
|
return if @{$self->{stack}} == 0; # only match this if stack non-empty |
|
780
|
|
|
|
|
1462
|
|
426
|
|
|
|
|
|
|
|
427
|
780
|
|
|
|
|
717
|
my $graph = $self->{_graph}; |
428
|
780
|
|
|
|
|
1111
|
my $eg = $1; # entire edge ("-- label -->" etc) |
429
|
|
|
|
|
|
|
|
430
|
780
|
|
66
|
|
|
1915
|
my $edge_bd = $2 || $4; # bidirectional edge ('<') ? |
431
|
780
|
|
|
|
|
587
|
my $edge_un = 0; # undirected edge? |
432
|
780
|
100
|
66
|
|
|
1256
|
$edge_un = 1 if !defined $2 && !defined $5; |
433
|
|
|
|
|
|
|
|
434
|
|
|
|
|
|
|
# optional edge label |
435
|
780
|
|
|
|
|
744
|
my $edge_label = $7; |
436
|
780
|
|
66
|
|
|
1599
|
my $ed = $3 || $5 || $1; # edge pattern/style ("--") |
437
|
|
|
|
|
|
|
|
438
|
780
|
|
100
|
|
|
2081
|
my $edge_atr = $11 || ''; # save edge attributes |
439
|
|
|
|
|
|
|
|
440
|
780
|
|
|
|
|
760
|
my $n = $12; # node name |
441
|
780
|
|
100
|
|
|
2707
|
my $a1 = $self->_parse_attributes($13||''); # node attributes |
442
|
|
|
|
|
|
|
|
443
|
780
|
|
|
|
|
1099
|
$edge_atr = $self->_parse_attributes($edge_atr, 'edge'); |
444
|
780
|
50
|
|
|
|
1166
|
return undef if $self->{error}; |
445
|
|
|
|
|
|
|
|
446
|
|
|
|
|
|
|
# allow undefined edge labels for setting them from the class |
447
|
|
|
|
|
|
|
# strip trailing spaces and convert \[ => [ |
448
|
780
|
100
|
|
|
|
1097
|
$edge_label = $self->_unquote($edge_label) if defined $edge_label; |
449
|
|
|
|
|
|
|
# strip trailing spaces |
450
|
780
|
100
|
|
|
|
1070
|
$edge_label =~ s/\s+\z// if defined $edge_label; |
451
|
|
|
|
|
|
|
|
452
|
|
|
|
|
|
|
# the right side node(s) (multiple in case of autosplit) |
453
|
780
|
|
|
|
|
1397
|
my $nodes_b = [ $self->_new_node ($self->{_graph}, $n, $self->{group_stack}, $a1) ]; |
454
|
|
|
|
|
|
|
|
455
|
780
|
|
|
|
|
1661
|
my $style = $self->_link_lists( $self->{stack}, $nodes_b, |
456
|
|
|
|
|
|
|
$ed, $edge_label, $edge_atr, $edge_bd, $edge_un); |
457
|
|
|
|
|
|
|
|
458
|
|
|
|
|
|
|
# remember the left side |
459
|
780
|
|
|
|
|
1616
|
$self->{left_edge} = [ $style, $edge_label, $edge_atr, $edge_bd, $edge_un ]; |
460
|
780
|
|
|
|
|
952
|
$self->{left_stack} = $self->{stack}; |
461
|
|
|
|
|
|
|
|
462
|
|
|
|
|
|
|
# forget stack and remember the right side instead |
463
|
780
|
|
|
|
|
791
|
$self->{stack} = $nodes_b; |
464
|
780
|
|
|
|
|
1286
|
1; |
465
|
324
|
|
|
|
|
4333
|
} ); |
466
|
|
|
|
|
|
|
|
467
|
324
|
|
|
|
|
511
|
my $qr_group_start = $self->_match_group_start(); |
468
|
|
|
|
|
|
|
|
469
|
|
|
|
|
|
|
# Things like ")" will be consumed before, so we do not need a case |
470
|
|
|
|
|
|
|
# for ") -> { ... } ( Group [ B ]": |
471
|
|
|
|
|
|
|
# edge to a group like "-> { ... } ( Group [" |
472
|
|
|
|
|
|
|
$self->_register_handler( qr/^$qr_edge$qr_oatr$qr_group_start/, |
473
|
|
|
|
|
|
|
sub |
474
|
|
|
|
|
|
|
{ |
475
|
6
|
|
|
6
|
|
8
|
my $self = shift; |
476
|
|
|
|
|
|
|
|
477
|
6
|
50
|
|
|
|
5
|
return if @{$self->{stack}} == 0; # only match this if stack non-empty |
|
6
|
|
|
|
|
14
|
|
478
|
|
|
|
|
|
|
|
479
|
6
|
|
|
|
|
11
|
my $eg = $1; # entire edge ("-- label -->" etc) |
480
|
|
|
|
|
|
|
|
481
|
6
|
|
33
|
|
|
17
|
my $edge_bd = $2 || $4; # bidirectional edge ('<') ? |
482
|
6
|
|
|
|
|
4
|
my $edge_un = 0; # undirected edge? |
483
|
6
|
0
|
33
|
|
|
11
|
$edge_un = 1 if !defined $2 && !defined $5; |
484
|
|
|
|
|
|
|
|
485
|
|
|
|
|
|
|
# optional edge label |
486
|
6
|
|
|
|
|
7
|
my $edge_label = $7; |
487
|
6
|
|
0
|
|
|
10
|
my $ed = $3 || $5 || $1; # edge pattern/style ("--") |
488
|
|
|
|
|
|
|
|
489
|
6
|
|
50
|
|
|
20
|
my $edge_atr = $11 || ''; # save edge attributes |
490
|
|
|
|
|
|
|
|
491
|
6
|
|
|
|
|
5
|
my $gn = $12; |
492
|
|
|
|
|
|
|
# matched "-> ( Group [" or "-> ( Group (" |
493
|
6
|
50
|
33
|
|
|
22
|
$self->{replace} = '[' if defined $13 && $13 eq '['; |
494
|
6
|
50
|
33
|
|
|
20
|
$self->{replace} = '(' if defined $13 && $13 eq '('; |
495
|
|
|
|
|
|
|
|
496
|
6
|
|
|
|
|
13
|
$edge_atr = $self->_parse_attributes($edge_atr, 'edge'); |
497
|
6
|
50
|
|
|
|
12
|
return undef if $self->{error}; |
498
|
|
|
|
|
|
|
|
499
|
|
|
|
|
|
|
# get the last group of the stack, lest the new one gets nested in it |
500
|
6
|
|
|
|
|
5
|
pop @{$self->{group_stack}}; |
|
6
|
|
|
|
|
8
|
|
501
|
|
|
|
|
|
|
|
502
|
6
|
|
|
|
|
8
|
$self->{group_stack} = [ $self->_new_group($gn) ]; |
503
|
|
|
|
|
|
|
|
504
|
|
|
|
|
|
|
# allow undefined edge labels for setting them from the class |
505
|
6
|
50
|
|
|
|
10
|
$edge_label = $self->_unquote($edge_label) if $edge_label; |
506
|
|
|
|
|
|
|
# strip trailing spaces |
507
|
6
|
50
|
|
|
|
9
|
$edge_label =~ s/\s+\z// if $edge_label; |
508
|
|
|
|
|
|
|
|
509
|
|
|
|
|
|
|
my $style = $self->_link_lists( $self->{stack}, $self->{group_stack}, |
510
|
6
|
|
|
|
|
12
|
$ed, $edge_label, $edge_atr, $edge_bd, $edge_un); |
511
|
|
|
|
|
|
|
|
512
|
|
|
|
|
|
|
# remember the left side |
513
|
6
|
|
|
|
|
11
|
$self->{left_edge} = [ $style, $edge_label, $edge_atr, $edge_bd, $edge_un ]; |
514
|
6
|
|
|
|
|
7
|
$self->{left_stack} = $self->{stack}; |
515
|
|
|
|
|
|
|
# forget stack |
516
|
6
|
|
|
|
|
6
|
$self->{stack} = []; |
517
|
|
|
|
|
|
|
# matched "->()" so remember the group on the stack |
518
|
6
|
50
|
33
|
|
|
30
|
$self->{stack} = [ $self->{group_stack}->[-1] ] if defined $13 && $13 eq ')'; |
519
|
|
|
|
|
|
|
|
520
|
6
|
|
|
|
|
10
|
1; |
521
|
324
|
|
|
|
|
2882
|
} ); |
522
|
|
|
|
|
|
|
} |
523
|
|
|
|
|
|
|
|
524
|
|
|
|
|
|
|
sub _line_insert |
525
|
|
|
|
|
|
|
{ |
526
|
|
|
|
|
|
|
# what to insert between two lines, '' for Graph::Easy, ' ' for Graphviz; |
527
|
962
|
|
|
962
|
|
2036
|
''; |
528
|
|
|
|
|
|
|
} |
529
|
|
|
|
|
|
|
|
530
|
|
|
|
|
|
|
sub _clean_line |
531
|
|
|
|
|
|
|
{ |
532
|
|
|
|
|
|
|
# do some cleanups on a line before handling it |
533
|
962
|
|
|
962
|
|
1016
|
my ($self,$line) = @_; |
534
|
|
|
|
|
|
|
|
535
|
962
|
|
|
|
|
1157
|
chomp($line); |
536
|
|
|
|
|
|
|
|
537
|
|
|
|
|
|
|
# convert #808080 into \#808080, and "#fff" into "\#fff" |
538
|
962
|
|
|
|
|
904
|
my $sep = $self->{attr_sep}; |
539
|
962
|
|
|
|
|
2693
|
$line =~ s/$sep\s*("?)(#(?:[a-fA-F0-9]{6}|[a-fA-F0-9]{3}))("?)/$sep $1\\$2$3/g; |
540
|
|
|
|
|
|
|
|
541
|
|
|
|
|
|
|
# remove comment at end of line (but leave \# alone): |
542
|
962
|
|
|
|
|
2382
|
$line =~ s/(:[^\\]|)$self->{qr_comment}.*/$1/; |
543
|
|
|
|
|
|
|
|
544
|
|
|
|
|
|
|
# remove white space at end (but not at the start, to keep " ||" intact |
545
|
962
|
|
|
|
|
2591
|
$line =~ s/\s+\z//; |
546
|
|
|
|
|
|
|
|
547
|
|
|
|
|
|
|
# print STDERR "# at line '$line' stack: ", join(",",@{ $self->{stack}}),"\n"; |
548
|
|
|
|
|
|
|
|
549
|
962
|
|
|
|
|
2319
|
$line; |
550
|
|
|
|
|
|
|
} |
551
|
|
|
|
|
|
|
|
552
|
|
|
|
|
|
|
sub from_text |
553
|
|
|
|
|
|
|
{ |
554
|
440
|
|
|
440
|
1
|
6585
|
my ($self,$txt) = @_; |
555
|
|
|
|
|
|
|
|
556
|
|
|
|
|
|
|
# matches a multi-line comment |
557
|
440
|
|
|
|
|
1442
|
my $o_cmt = qr#((\s*/\*.*?\*/\s*)*\s*|\s+)#; |
558
|
|
|
|
|
|
|
|
559
|
440
|
100
|
66
|
|
|
12234
|
if ((ref($self)||$self) eq 'Graph::Easy::Parser' && |
|
|
|
100
|
|
|
|
|
|
|
|
66
|
|
|
|
|
560
|
|
|
|
|
|
|
# contains "digraph GRAPH {" or something similar |
561
|
|
|
|
|
|
|
( $txt =~ /^(\s*|\s*\/\*.*?\*\/\s*)(strict)?$o_cmt(di)?graph$o_cmt("[^"]*"|[\w_]+)$o_cmt\{/im || |
562
|
|
|
|
|
|
|
# contains "digraph {" or something similar |
563
|
|
|
|
|
|
|
$txt =~ /^(\s*|\s*\/\*.*?\*\/\s*)(strict)?${o_cmt}digraph$o_cmt\{/im || |
564
|
|
|
|
|
|
|
# contains "strict graph {" or something similar |
565
|
|
|
|
|
|
|
$txt =~ /^(\s*|\s*\/\*.*?\*\/\s*)strict${o_cmt}(di)?graph$o_cmt\{/im)) |
566
|
|
|
|
|
|
|
{ |
567
|
41
|
|
|
|
|
509
|
require Graph::Easy::Parser::Graphviz; |
568
|
|
|
|
|
|
|
# recreate ourselfes, and pass our arguments along |
569
|
41
|
|
|
|
|
46
|
my $debug = 0; |
570
|
41
|
|
|
|
|
37
|
my $old_self = $self; |
571
|
41
|
100
|
|
|
|
73
|
if (ref($self)) |
572
|
|
|
|
|
|
|
{ |
573
|
40
|
|
|
|
|
55
|
$debug = $self->{debug}; |
574
|
40
|
|
|
|
|
48
|
$self->{fatal_errors} = 0; |
575
|
|
|
|
|
|
|
} |
576
|
41
|
|
|
|
|
182
|
$self = Graph::Easy::Parser::Graphviz->new( debug => $debug, fatal_errors => 0 ); |
577
|
41
|
|
|
|
|
92
|
$self->reset(); |
578
|
41
|
50
|
|
|
|
106
|
$self->{_old_self} = $old_self if ref($self); |
579
|
|
|
|
|
|
|
} |
580
|
|
|
|
|
|
|
|
581
|
440
|
100
|
66
|
|
|
2428
|
if ((ref($self)||$self) eq 'Graph::Easy::Parser' && |
|
|
|
100
|
|
|
|
|
582
|
|
|
|
|
|
|
# contains "graph: {" |
583
|
|
|
|
|
|
|
$txt =~ /^([\s\n\t]*|\s*\/\*.*?\*\/\s*)graph\s*:\s*\{/m) |
584
|
|
|
|
|
|
|
{ |
585
|
13
|
|
|
|
|
337
|
require Graph::Easy::Parser::VCG; |
586
|
|
|
|
|
|
|
# recreate ourselfes, and pass our arguments along |
587
|
13
|
|
|
|
|
14
|
my $debug = 0; |
588
|
13
|
|
|
|
|
10
|
my $old_self = $self; |
589
|
13
|
50
|
|
|
|
32
|
if (ref($self)) |
590
|
|
|
|
|
|
|
{ |
591
|
13
|
|
|
|
|
19
|
$debug = $self->{debug}; |
592
|
13
|
|
|
|
|
18
|
$self->{fatal_errors} = 0; |
593
|
|
|
|
|
|
|
} |
594
|
13
|
|
|
|
|
58
|
$self = Graph::Easy::Parser::VCG->new( debug => $debug, fatal_errors => 0 ); |
595
|
13
|
|
|
|
|
31
|
$self->reset(); |
596
|
13
|
50
|
|
|
|
36
|
$self->{_old_self} = $old_self if ref($self); |
597
|
|
|
|
|
|
|
} |
598
|
|
|
|
|
|
|
|
599
|
440
|
100
|
|
|
|
850
|
$self = $self->new() unless ref $self; |
600
|
440
|
|
|
|
|
1021
|
$self->reset(); |
601
|
|
|
|
|
|
|
|
602
|
440
|
|
|
|
|
565
|
my $graph = $self->{_graph}; |
603
|
440
|
100
|
66
|
|
|
2527
|
return $graph if !defined $txt || $txt =~ /^\s*\z/; # empty text? |
604
|
|
|
|
|
|
|
|
605
|
439
|
|
|
|
|
562
|
my $uc = $self->{use_class}; |
606
|
|
|
|
|
|
|
|
607
|
|
|
|
|
|
|
# instruct the graph to use the custom classes, too |
608
|
439
|
|
|
|
|
1416
|
for my $o (sort keys %$uc) |
609
|
|
|
|
|
|
|
{ |
610
|
1756
|
100
|
|
|
|
4172
|
$graph->use_class($o, $uc->{$o}) unless $o eq 'graph'; # group, node and edge |
611
|
|
|
|
|
|
|
} |
612
|
|
|
|
|
|
|
|
613
|
439
|
|
|
|
|
3416
|
my @lines = split /(\r\n|\n|\r)/, $txt; |
614
|
|
|
|
|
|
|
|
615
|
439
|
|
|
|
|
529
|
my $backbuffer = ''; # left over fragments to be combined with next line |
616
|
|
|
|
|
|
|
|
617
|
439
|
|
|
|
|
923
|
my $qr_comment = $self->_match_commented_line(); |
618
|
439
|
|
|
|
|
893
|
$self->{qr_comment} = $self->_match_comment(); |
619
|
|
|
|
|
|
|
# cache the value of this since it can be expensive to construct: |
620
|
439
|
|
|
|
|
1108
|
$self->{_match_single_attribute} = $self->_match_single_attribute(); |
621
|
|
|
|
|
|
|
|
622
|
439
|
|
|
|
|
1026
|
$self->_build_match_stack(); |
623
|
|
|
|
|
|
|
|
624
|
|
|
|
|
|
|
########################################################################### |
625
|
|
|
|
|
|
|
# main parsing loop |
626
|
|
|
|
|
|
|
|
627
|
439
|
|
|
|
|
541
|
my $handled = 0; # did we handle a fragment? |
628
|
439
|
|
|
|
|
350
|
my $line; |
629
|
|
|
|
|
|
|
|
630
|
|
|
|
|
|
|
# my $counts = {}; |
631
|
|
|
|
|
|
|
LINE: |
632
|
439
|
|
100
|
|
|
1228
|
while (@lines > 0 || $backbuffer ne '') |
633
|
|
|
|
|
|
|
{ |
634
|
|
|
|
|
|
|
# only accumulate more text if we didn't handle a fragment |
635
|
5902
|
100
|
100
|
|
|
16951
|
if (@lines > 0 && $handled == 0) |
636
|
|
|
|
|
|
|
{ |
637
|
3573
|
|
|
|
|
2724
|
$self->{line_nr}++; |
638
|
3573
|
|
|
|
|
3044
|
my $curline = shift @lines; |
639
|
|
|
|
|
|
|
|
640
|
|
|
|
|
|
|
# discard empty lines, or completely commented out lines |
641
|
3573
|
100
|
|
|
|
14390
|
next if $curline =~ $qr_comment; |
642
|
|
|
|
|
|
|
|
643
|
|
|
|
|
|
|
# convert tabs to spaces (the regexps don't expect tabs) |
644
|
1554
|
|
|
|
|
2405
|
$curline =~ tr/\t/ /; |
645
|
|
|
|
|
|
|
|
646
|
|
|
|
|
|
|
# combine backbuffer, what to insert between two lines and next line: |
647
|
1554
|
|
|
|
|
2848
|
$line = $backbuffer . $self->_line_insert() . $self->_clean_line($curline); |
648
|
|
|
|
|
|
|
} |
649
|
|
|
|
|
|
|
|
650
|
3883
|
50
|
33
|
|
|
7085
|
print STDERR "# Line is '$line'\n" if $self->{debug} && $self->{debug} > 2; |
651
|
3883
|
50
|
33
|
|
|
6010
|
print STDERR "# Backbuffer is '$backbuffer'\n" if $self->{debug} && $self->{debug} > 2; |
652
|
|
|
|
|
|
|
|
653
|
3883
|
|
|
|
|
2809
|
$handled = 0; |
654
|
|
|
|
|
|
|
#debug my $count = 0; |
655
|
|
|
|
|
|
|
PATTERN: |
656
|
3883
|
|
|
|
|
2977
|
for my $entry (@{$self->{match_stack}}) |
|
3883
|
|
|
|
|
5530
|
|
657
|
|
|
|
|
|
|
{ |
658
|
|
|
|
|
|
|
# nothing to match against? |
659
|
19921
|
100
|
|
|
|
22948
|
last PATTERN if $line eq ''; |
660
|
|
|
|
|
|
|
|
661
|
18667
|
|
|
|
|
14057
|
$self->{replace} = ''; # as default just remove the matched text |
662
|
18667
|
|
|
|
|
16487
|
my ($pattern, $handler, $replace) = @$entry; |
663
|
|
|
|
|
|
|
|
664
|
18667
|
50
|
33
|
|
|
25848
|
print STDERR "# Matching against $pattern\n" if $self->{debug} && $self->{debug} > 3; |
665
|
|
|
|
|
|
|
|
666
|
18667
|
100
|
|
|
|
94810
|
if ($line =~ $pattern) |
667
|
|
|
|
|
|
|
{ |
668
|
|
|
|
|
|
|
#debug $counts->{$count}++; |
669
|
2693
|
50
|
33
|
|
|
4916
|
print STDERR "# Matched, calling handler\n" if $self->{debug} && $self->{debug} > 2; |
670
|
2693
|
|
|
|
|
2061
|
my $rc = 1; |
671
|
2693
|
100
|
|
|
|
5807
|
$rc = &$handler($self) if defined $handler; |
672
|
2693
|
100
|
|
|
|
4280
|
if ($rc) |
673
|
|
|
|
|
|
|
{ |
674
|
2514
|
100
|
|
|
|
4150
|
$replace = $self->{replace} unless defined $replace; |
675
|
2514
|
100
|
|
|
|
3305
|
$replace = &$replace($self,$line) if ref($replace); |
676
|
2514
|
50
|
33
|
|
|
4219
|
print STDERR "# Handled it successfully.\n" if $self->{debug} && $self->{debug} > 2; |
677
|
2514
|
|
|
|
|
23835
|
$line =~ s/$pattern/$replace/; |
678
|
2514
|
50
|
33
|
|
|
4958
|
print STDERR "# Line is now '$line' (replaced with '$replace')\n" if $self->{debug} && $self->{debug} > 2; |
679
|
2514
|
|
|
|
|
1878
|
$handled++; last PATTERN; |
|
2514
|
|
|
|
|
3651
|
|
680
|
|
|
|
|
|
|
} |
681
|
|
|
|
|
|
|
} |
682
|
|
|
|
|
|
|
#debug $count ++; |
683
|
|
|
|
|
|
|
|
684
|
|
|
|
|
|
|
} |
685
|
|
|
|
|
|
|
|
686
|
|
|
|
|
|
|
#debug if ($handled == 0) { $counts->{'-1'}++; } |
687
|
|
|
|
|
|
|
# couldn't handle that fragment, so accumulate it and try again |
688
|
3883
|
|
|
|
|
3557
|
$backbuffer = $line; |
689
|
|
|
|
|
|
|
|
690
|
|
|
|
|
|
|
# stop at the very last line |
691
|
3883
|
100
|
100
|
|
|
8408
|
last LINE if $handled == 0 && @lines == 0; |
692
|
|
|
|
|
|
|
|
693
|
|
|
|
|
|
|
# stop at parsing errors |
694
|
3871
|
50
|
|
|
|
10248
|
last LINE if $self->{error}; |
695
|
|
|
|
|
|
|
} |
696
|
|
|
|
|
|
|
|
697
|
439
|
100
|
|
|
|
835
|
$self->error("'$backbuffer' not recognized by " . ref($self)) if $backbuffer ne ''; |
698
|
|
|
|
|
|
|
|
699
|
|
|
|
|
|
|
# if something was left on the stack, file ended unexpectedly |
700
|
439
|
50
|
66
|
|
|
1423
|
$self->parse_error(7) if !$self->{error} && $self->{scope_stack} && @{$self->{scope_stack}} > 0; |
|
115
|
|
66
|
|
|
283
|
|
701
|
|
|
|
|
|
|
|
702
|
439
|
50
|
66
|
|
|
789
|
return undef if $self->{error} && $self->{fatal_errors}; |
703
|
|
|
|
|
|
|
|
704
|
|
|
|
|
|
|
#debug use Data::Dumper; print Dumper($counts); |
705
|
|
|
|
|
|
|
|
706
|
439
|
50
|
|
|
|
711
|
print STDERR "# Parsing done.\n" if $graph->{debug}; |
707
|
|
|
|
|
|
|
|
708
|
|
|
|
|
|
|
# Do final cleanup (for parsing Graphviz) |
709
|
439
|
50
|
|
|
|
1858
|
$self->_parser_cleanup() if $self->can('_parser_cleanup'); |
710
|
439
|
|
|
|
|
1033
|
$graph->_drop_special_attributes(); |
711
|
|
|
|
|
|
|
|
712
|
|
|
|
|
|
|
# turn on strict checking on returned graph |
713
|
439
|
|
|
|
|
1358
|
$graph->strict(1); |
714
|
439
|
|
|
|
|
890
|
$graph->fatal_errors(1); |
715
|
|
|
|
|
|
|
|
716
|
439
|
|
|
|
|
4642
|
$graph; |
717
|
|
|
|
|
|
|
} |
718
|
|
|
|
|
|
|
|
719
|
|
|
|
|
|
|
############################################################################# |
720
|
|
|
|
|
|
|
# internal routines |
721
|
|
|
|
|
|
|
|
722
|
|
|
|
|
|
|
sub _edge_style |
723
|
|
|
|
|
|
|
{ |
724
|
786
|
|
|
786
|
|
650
|
my ($self, $ed) = @_; |
725
|
|
|
|
|
|
|
|
726
|
786
|
|
|
|
|
626
|
my $style = undef; # default is "inherit from class" |
727
|
786
|
100
|
|
|
|
1237
|
$style = 'double-dash' if $ed =~ /^(= )+\z/; |
728
|
786
|
100
|
|
|
|
1219
|
$style = 'double' if $ed =~ /^=+\z/; |
729
|
786
|
100
|
|
|
|
1216
|
$style = 'dotted' if $ed =~ /^\.+\z/; |
730
|
786
|
100
|
|
|
|
1016
|
$style = 'dashed' if $ed =~ /^(- )+\z/; |
731
|
786
|
100
|
|
|
|
1035
|
$style = 'dot-dot-dash' if $ed =~ /^(..-)+\z/; |
732
|
786
|
100
|
|
|
|
1203
|
$style = 'dot-dash' if $ed =~ /^(\.-)+\z/; |
733
|
786
|
100
|
|
|
|
1090
|
$style = 'wave' if $ed =~ /^\~+\z/; |
734
|
786
|
50
|
|
|
|
1148
|
$style = 'bold' if $ed =~ /^#+\z/; |
735
|
|
|
|
|
|
|
|
736
|
786
|
|
|
|
|
825
|
$style; |
737
|
|
|
|
|
|
|
} |
738
|
|
|
|
|
|
|
|
739
|
|
|
|
|
|
|
sub _link_lists |
740
|
|
|
|
|
|
|
{ |
741
|
|
|
|
|
|
|
# Given two node lists and an edge style, links each node from list |
742
|
|
|
|
|
|
|
# one to list two. |
743
|
904
|
|
|
904
|
|
1161
|
my ($self, $left, $right, $ed, $label, $edge_atr, $edge_bd, $edge_un) = @_; |
744
|
|
|
|
|
|
|
|
745
|
904
|
|
|
|
|
801
|
my $graph = $self->{_graph}; |
746
|
|
|
|
|
|
|
|
747
|
904
|
|
|
|
|
1301
|
my $style = $self->_edge_style($ed); |
748
|
904
|
|
|
|
|
1059
|
my $e = $self->{use_class}->{edge}; |
749
|
|
|
|
|
|
|
|
750
|
|
|
|
|
|
|
# add edges for all nodes in the left list |
751
|
904
|
|
|
|
|
1106
|
for my $node (@$left) |
752
|
|
|
|
|
|
|
{ |
753
|
964
|
|
|
|
|
893
|
for my $node_b (@$right) |
754
|
|
|
|
|
|
|
{ |
755
|
969
|
|
|
|
|
3524
|
my $edge = $e->new( { style => $style, name => $label } ); |
756
|
|
|
|
|
|
|
|
757
|
969
|
|
|
|
|
2682
|
$graph->add_edge ( $node, $node_b, $edge ); |
758
|
|
|
|
|
|
|
|
759
|
|
|
|
|
|
|
# 'string' => [ 'string' ] |
760
|
|
|
|
|
|
|
# [ { hash }, 'string' ] => [ { hash }, 'string' ] |
761
|
969
|
100
|
|
|
|
834
|
my $e = $edge_atr; $e = [ $edge_atr ] unless ref($e) eq 'ARRAY'; |
|
969
|
|
|
|
|
2132
|
|
762
|
|
|
|
|
|
|
|
763
|
969
|
|
|
|
|
1288
|
for my $a (@$e) |
764
|
|
|
|
|
|
|
{ |
765
|
1097
|
100
|
|
|
|
1401
|
if (ref $a) |
766
|
|
|
|
|
|
|
{ |
767
|
969
|
|
|
|
|
1758
|
$edge->set_attributes($a); |
768
|
|
|
|
|
|
|
} |
769
|
|
|
|
|
|
|
else |
770
|
|
|
|
|
|
|
{ |
771
|
|
|
|
|
|
|
# deferred parsing with the object as param: |
772
|
128
|
|
|
|
|
224
|
my $out = $self->_parse_attributes($a, $edge); |
773
|
128
|
50
|
|
|
|
255
|
return undef if $self->{error}; |
774
|
128
|
|
|
|
|
215
|
$edge->set_attributes($out); |
775
|
|
|
|
|
|
|
} |
776
|
|
|
|
|
|
|
} |
777
|
|
|
|
|
|
|
|
778
|
|
|
|
|
|
|
# "<--->": bidirectional |
779
|
969
|
100
|
|
|
|
1448
|
$edge->bidirectional(1) if $edge_bd; |
780
|
969
|
100
|
|
|
|
2101
|
$edge->undirected(1) if $edge_un; |
781
|
|
|
|
|
|
|
} |
782
|
|
|
|
|
|
|
} |
783
|
|
|
|
|
|
|
|
784
|
904
|
|
|
|
|
1359
|
$style; |
785
|
|
|
|
|
|
|
} |
786
|
|
|
|
|
|
|
|
787
|
|
|
|
|
|
|
sub _unquote_attribute |
788
|
|
|
|
|
|
|
{ |
789
|
611
|
|
|
611
|
|
689
|
my ($self,$name,$value) = @_; |
790
|
|
|
|
|
|
|
|
791
|
611
|
|
|
|
|
825
|
$self->_unquote($value); |
792
|
|
|
|
|
|
|
} |
793
|
|
|
|
|
|
|
|
794
|
|
|
|
|
|
|
sub _unquote |
795
|
|
|
|
|
|
|
{ |
796
|
2286
|
|
|
2286
|
|
2307
|
my ($self, $name, $no_collapse) = @_; |
797
|
|
|
|
|
|
|
|
798
|
2286
|
50
|
|
|
|
2856
|
$name = '' unless defined $name; |
799
|
|
|
|
|
|
|
|
800
|
|
|
|
|
|
|
# unquote special chars |
801
|
2286
|
|
|
|
|
2434
|
$name =~ s/\\([\[\(\{\}\]\)#<>\-\.\=])/$1/g; |
802
|
|
|
|
|
|
|
|
803
|
|
|
|
|
|
|
# collapse multiple spaces |
804
|
2286
|
100
|
|
|
|
3271
|
$name =~ s/\s+/ /g unless $no_collapse; |
805
|
|
|
|
|
|
|
|
806
|
2286
|
|
|
|
|
4079
|
$name; |
807
|
|
|
|
|
|
|
} |
808
|
|
|
|
|
|
|
|
809
|
|
|
|
|
|
|
sub _add_node |
810
|
|
|
|
|
|
|
{ |
811
|
|
|
|
|
|
|
# add a node to the graph, overidable by subclasses |
812
|
1507
|
|
|
1507
|
|
1490
|
my ($self, $graph, $name) = @_; |
813
|
|
|
|
|
|
|
|
814
|
1507
|
|
|
|
|
3395
|
$graph->add_node($name); # add unless exists |
815
|
|
|
|
|
|
|
} |
816
|
|
|
|
|
|
|
|
817
|
|
|
|
|
|
|
sub _get_cluster_name |
818
|
|
|
|
|
|
|
{ |
819
|
|
|
|
|
|
|
# create a unique name for an autosplit node |
820
|
65
|
|
|
65
|
|
79
|
my ($self, $base_name) = @_; |
821
|
|
|
|
|
|
|
|
822
|
|
|
|
|
|
|
# Try to find a unique cluster name in case some one get's creative and names the |
823
|
|
|
|
|
|
|
# last part "-1": |
824
|
|
|
|
|
|
|
|
825
|
|
|
|
|
|
|
# does work without cluster-id? |
826
|
65
|
100
|
|
|
|
142
|
if (exists $self->{clusters}->{$base_name}) |
827
|
|
|
|
|
|
|
{ |
828
|
2
|
|
|
|
|
3
|
my $g = 1; |
829
|
2
|
|
|
|
|
7
|
while ($g == 1) |
830
|
|
|
|
|
|
|
{ |
831
|
5
|
100
|
|
|
|
5
|
my $base_try = $base_name; $base_try .= '-' . $self->{cluster_id} if $self->{cluster_id}; |
|
5
|
|
|
|
|
11
|
|
832
|
5
|
100
|
|
|
|
10
|
last if !exists $self->{clusters}->{$base_try}; |
833
|
3
|
|
|
|
|
5
|
$self->{cluster_id}++; |
834
|
|
|
|
|
|
|
} |
835
|
2
|
50
|
|
|
|
6
|
$base_name .= '-' . $self->{cluster_id} if $self->{cluster_id}; $self->{cluster_id}++; |
|
2
|
|
|
|
|
2
|
|
836
|
|
|
|
|
|
|
} |
837
|
|
|
|
|
|
|
|
838
|
65
|
|
|
|
|
127
|
$self->{clusters}->{$base_name} = undef; # reserve this name |
839
|
|
|
|
|
|
|
|
840
|
65
|
|
|
|
|
92
|
$base_name; |
841
|
|
|
|
|
|
|
} |
842
|
|
|
|
|
|
|
|
843
|
|
|
|
|
|
|
sub _set_new_basename |
844
|
|
|
|
|
|
|
{ |
845
|
|
|
|
|
|
|
# when encountering something like: |
846
|
|
|
|
|
|
|
# [ a | b ] |
847
|
|
|
|
|
|
|
# { basename: foo; } |
848
|
|
|
|
|
|
|
# the Parser will create two nodes, ab.0 and ab.1, and then later see |
849
|
|
|
|
|
|
|
# the "basename: foo". Sowe need to rename the already created nodes |
850
|
|
|
|
|
|
|
# due to the changed basename: |
851
|
2
|
|
|
2
|
|
3
|
my ($self, $node, $new_basename) = @_; |
852
|
|
|
|
|
|
|
|
853
|
|
|
|
|
|
|
# nothing changes? |
854
|
2
|
50
|
|
|
|
7
|
return if $node->{autosplit_basename} eq $new_basename; |
855
|
|
|
|
|
|
|
|
856
|
2
|
|
|
|
|
2
|
my $g = $node->{graph}; |
857
|
|
|
|
|
|
|
|
858
|
2
|
|
|
|
|
2
|
my @parts = @{$node->{autosplit_parts}}; |
|
2
|
|
|
|
|
5
|
|
859
|
2
|
|
|
|
|
3
|
my $nr = 0; |
860
|
2
|
|
|
|
|
4
|
for my $part ($node, @parts) |
861
|
|
|
|
|
|
|
{ |
862
|
|
|
|
|
|
|
print STDERR "# Setting new basename $new_basename for node $part->{name}\n" |
863
|
6
|
50
|
|
|
|
14
|
if $self->{debug} > 1; |
864
|
|
|
|
|
|
|
|
865
|
6
|
|
|
|
|
4
|
$part->{autosplit_basename} = $new_basename; |
866
|
6
|
|
|
|
|
13
|
$part->set_attribute('basename', $new_basename); |
867
|
|
|
|
|
|
|
|
868
|
|
|
|
|
|
|
# delete it from the list of nodes |
869
|
6
|
|
|
|
|
12
|
delete $g->{nodes}->{$part->{name}}; |
870
|
6
|
|
|
|
|
12
|
$part->{name} = $new_basename . '.' . $nr; $nr++; |
|
6
|
|
|
|
|
5
|
|
871
|
|
|
|
|
|
|
# and re-insert it with the right name |
872
|
6
|
|
|
|
|
12
|
$g->{nodes}->{$part->{name}} = $part; |
873
|
|
|
|
|
|
|
|
874
|
|
|
|
|
|
|
# we do not need to care for edges here, as they are stored with refs |
875
|
|
|
|
|
|
|
# to the nodes and not the node names itself |
876
|
|
|
|
|
|
|
} |
877
|
|
|
|
|
|
|
} |
878
|
|
|
|
|
|
|
|
879
|
|
|
|
|
|
|
sub _autosplit_node |
880
|
|
|
|
|
|
|
{ |
881
|
|
|
|
|
|
|
# Takes a node name like "a|b||c" and splits it into "a", "b", and "c". |
882
|
|
|
|
|
|
|
# Returns the individual parts. |
883
|
65
|
|
|
65
|
|
93
|
my ($self, $graph, $name, $att, $allow_empty) = @_; |
884
|
|
|
|
|
|
|
|
885
|
|
|
|
|
|
|
# Default is to have empty parts. Graphviz sets this to true; |
886
|
65
|
100
|
|
|
|
132
|
$allow_empty = 1 unless defined $allow_empty; |
887
|
|
|
|
|
|
|
|
888
|
65
|
|
|
|
|
67
|
my @rc; |
889
|
65
|
|
|
|
|
73
|
my $uc = $self->{use_class}; |
890
|
65
|
|
|
|
|
70
|
my $qr_clean = $self->{_qr_part_clean}; |
891
|
|
|
|
|
|
|
|
892
|
|
|
|
|
|
|
# build base name: "A|B |C||D" => "ABCD" |
893
|
65
|
|
|
|
|
72
|
my $base_name = $name; $base_name =~ s/\s*\|\|?\s*//g; |
|
65
|
|
|
|
|
362
|
|
894
|
|
|
|
|
|
|
|
895
|
|
|
|
|
|
|
# use user-provided base name |
896
|
65
|
100
|
|
|
|
156
|
$base_name = $att->{basename} if exists $att->{basename}; |
897
|
|
|
|
|
|
|
|
898
|
|
|
|
|
|
|
# strip trailing/leading spaces on basename |
899
|
65
|
|
|
|
|
138
|
$base_name =~ s/\s+\z//; |
900
|
65
|
|
|
|
|
107
|
$base_name =~ s/^\s+//; |
901
|
|
|
|
|
|
|
|
902
|
|
|
|
|
|
|
# first one gets: "ABC", second one "ABC.1" and so on |
903
|
65
|
|
|
|
|
128
|
$base_name = $self->_get_cluster_name($base_name); |
904
|
|
|
|
|
|
|
|
905
|
65
|
50
|
|
|
|
119
|
print STDERR "# Parser: Autosplitting node with basename '$base_name'\n" if $graph->{debug}; |
906
|
|
|
|
|
|
|
|
907
|
65
|
|
|
|
|
55
|
my $first_in_row; # for relative placement of new row |
908
|
65
|
|
|
|
|
51
|
my $x = 0; my $y = 0; my $idx = 0; |
|
65
|
|
|
|
|
51
|
|
|
65
|
|
|
|
|
62
|
|
909
|
65
|
|
|
|
|
56
|
my $remaining = $name; my $sep; my $last_sep = ''; |
|
65
|
|
|
|
|
56
|
|
|
65
|
|
|
|
|
59
|
|
910
|
65
|
|
|
|
|
69
|
my $add = 0; |
911
|
65
|
|
|
|
|
120
|
while ($remaining ne '') |
912
|
|
|
|
|
|
|
{ |
913
|
|
|
|
|
|
|
# XXX TODO: parsing of "\|" and "|" in one node |
914
|
188
|
|
|
|
|
693
|
$remaining =~ s/^((\\\||[^\|])*)(\|\|?|\z)//; |
915
|
188
|
|
100
|
|
|
495
|
my $part = $1 || ' '; |
916
|
188
|
|
|
|
|
173
|
$sep = $3; |
917
|
188
|
|
|
|
|
145
|
my $port_name = ''; |
918
|
|
|
|
|
|
|
|
919
|
|
|
|
|
|
|
# possible cleanup for this part |
920
|
188
|
100
|
|
|
|
269
|
if ($qr_clean) |
921
|
|
|
|
|
|
|
{ |
922
|
45
|
|
|
|
|
169
|
$part =~ s/^$qr_clean//; $port_name = $1; |
|
45
|
|
|
|
|
58
|
|
923
|
|
|
|
|
|
|
} |
924
|
|
|
|
|
|
|
|
925
|
|
|
|
|
|
|
# fix [|G|] to have one empty part as last part |
926
|
188
|
100
|
100
|
|
|
813
|
if ($add == 0 && $remaining eq '' && $sep =~ /\|\|?/) |
|
|
|
100
|
|
|
|
|
927
|
|
|
|
|
|
|
{ |
928
|
2
|
|
|
|
|
1
|
$add++; # only do it once |
929
|
2
|
|
|
|
|
3
|
$remaining .= '|' |
930
|
|
|
|
|
|
|
} |
931
|
|
|
|
|
|
|
|
932
|
188
|
50
|
|
|
|
281
|
print STDERR "# Parser: Found autosplit part '$part'\n" if $graph->{debug}; |
933
|
|
|
|
|
|
|
|
934
|
188
|
|
|
|
|
179
|
my $class = $uc->{node}; |
935
|
188
|
100
|
100
|
|
|
616
|
if ($allow_empty && $part eq ' ') |
|
|
100
|
|
|
|
|
|
936
|
|
|
|
|
|
|
{ |
937
|
|
|
|
|
|
|
# create an empty node with no border |
938
|
12
|
|
|
|
|
31
|
$class .= "::Empty"; |
939
|
|
|
|
|
|
|
} |
940
|
|
|
|
|
|
|
elsif ($part =~ /^[ ]{2,}\z/) |
941
|
|
|
|
|
|
|
{ |
942
|
|
|
|
|
|
|
# create an empty node with border |
943
|
10
|
|
|
|
|
12
|
$part = ' '; |
944
|
|
|
|
|
|
|
} |
945
|
|
|
|
|
|
|
else |
946
|
|
|
|
|
|
|
{ |
947
|
166
|
|
|
|
|
290
|
$part =~ s/^\s+//; # rem spaces at front |
948
|
166
|
|
|
|
|
299
|
$part =~ s/\s+\z//; # rem spaces at end |
949
|
|
|
|
|
|
|
} |
950
|
|
|
|
|
|
|
|
951
|
188
|
|
|
|
|
299
|
my $node_name = "$base_name.$idx"; |
952
|
|
|
|
|
|
|
|
953
|
188
|
50
|
|
|
|
301
|
if ($graph->{debug}) |
954
|
|
|
|
|
|
|
{ |
955
|
0
|
|
|
|
|
0
|
my $empty = ''; |
956
|
0
|
0
|
|
|
|
0
|
$empty = ' empty' if $class ne $self->{use_class}->{node}; |
957
|
0
|
0
|
|
|
|
0
|
print STDERR "# Parser: Creating$empty autosplit part '$part'\n" if $graph->{debug}; |
958
|
|
|
|
|
|
|
} |
959
|
|
|
|
|
|
|
|
960
|
|
|
|
|
|
|
# if it doesn't exist, add it, otherwise retrieve node object to $node |
961
|
188
|
100
|
|
|
|
351
|
if ($class =~ /::Empty/) |
962
|
|
|
|
|
|
|
{ |
963
|
12
|
|
|
|
|
32
|
my $node = $graph->node($node_name); |
964
|
12
|
100
|
|
|
|
29
|
if (!defined $node) |
965
|
|
|
|
|
|
|
{ |
966
|
|
|
|
|
|
|
# create node object from the correct class |
967
|
11
|
|
|
|
|
46
|
$node = $class->new($node_name); |
968
|
11
|
|
|
|
|
22
|
$graph->add_node($node); |
969
|
|
|
|
|
|
|
} |
970
|
|
|
|
|
|
|
} |
971
|
|
|
|
|
|
|
|
972
|
188
|
|
|
|
|
402
|
my $node = $graph->add_node($node_name); |
973
|
188
|
|
|
|
|
226
|
$node->{autosplit_label} = $part; |
974
|
|
|
|
|
|
|
# remember these two for Graphviz |
975
|
188
|
|
|
|
|
204
|
$node->{autosplit_portname} = $port_name; |
976
|
188
|
|
|
|
|
302
|
$node->{autosplit_basename} = $base_name; |
977
|
|
|
|
|
|
|
|
978
|
188
|
|
|
|
|
193
|
push @rc, $node; |
979
|
188
|
100
|
|
|
|
264
|
if (@rc == 1) |
980
|
|
|
|
|
|
|
{ |
981
|
|
|
|
|
|
|
# for correct as_txt output |
982
|
65
|
|
|
|
|
105
|
$node->{autosplit} = $name; |
983
|
65
|
|
|
|
|
213
|
$node->{autosplit} =~ s/\s+\z//; # strip trailing spaces |
984
|
65
|
|
|
|
|
139
|
$node->{autosplit} =~ s/^\s+//; # strip leading spaces |
985
|
65
|
|
|
|
|
316
|
$node->{autosplit} =~ s/([^\|])\s+\|/$1 \|/g; # 'foo |' => 'foo |' |
986
|
65
|
|
|
|
|
251
|
$node->{autosplit} =~ s/\|\s+([^\|])/\| $1/g; # '| foo' => '| foo' |
987
|
65
|
100
|
|
|
|
160
|
$node->set_attribute('basename', $att->{basename}) if defined $att->{basename}; |
988
|
|
|
|
|
|
|
# list of all autosplit parts so as_txt() can find them easily again |
989
|
65
|
|
|
|
|
99
|
$node->{autosplit_parts} = [ ]; |
990
|
65
|
|
|
|
|
82
|
$first_in_row = $node; |
991
|
|
|
|
|
|
|
} |
992
|
|
|
|
|
|
|
else |
993
|
|
|
|
|
|
|
{ |
994
|
|
|
|
|
|
|
# second, third etc. get previous as origin |
995
|
123
|
|
|
|
|
131
|
my ($sx,$sy) = (1,0); |
996
|
123
|
|
|
|
|
235
|
my $origin = $rc[-2]; |
997
|
123
|
100
|
|
|
|
201
|
if ($last_sep eq '||') |
998
|
|
|
|
|
|
|
{ |
999
|
16
|
|
|
|
|
19
|
($sx,$sy) = (0,1); $origin = $first_in_row; |
|
16
|
|
|
|
|
15
|
|
1000
|
16
|
|
|
|
|
14
|
$first_in_row = $node; |
1001
|
|
|
|
|
|
|
} |
1002
|
123
|
|
|
|
|
276
|
$node->relative_to($origin,$sx,$sy); |
1003
|
123
|
|
|
|
|
86
|
push @{$rc[0]->{autosplit_parts}}, $node; |
|
123
|
|
|
|
|
218
|
|
1004
|
123
|
|
|
|
|
106
|
weaken @{$rc[0]->{autosplit_parts}}[-1]; |
|
123
|
|
|
|
|
274
|
|
1005
|
|
|
|
|
|
|
|
1006
|
|
|
|
|
|
|
# suppress as_txt output for other parts |
1007
|
123
|
|
|
|
|
142
|
$node->{autosplit} = undef; |
1008
|
|
|
|
|
|
|
} |
1009
|
|
|
|
|
|
|
# nec. for border-collapse |
1010
|
188
|
|
|
|
|
372
|
$node->{autosplit_xy} = "$x,$y"; |
1011
|
|
|
|
|
|
|
|
1012
|
188
|
|
|
|
|
177
|
$idx++; # next node ID |
1013
|
188
|
|
|
|
|
153
|
$last_sep = $sep; |
1014
|
188
|
|
|
|
|
160
|
$x++; |
1015
|
|
|
|
|
|
|
# || starts a new row: |
1016
|
188
|
100
|
|
|
|
475
|
if ($sep eq '||') |
1017
|
|
|
|
|
|
|
{ |
1018
|
16
|
|
|
|
|
23
|
$x = 0; $y++; |
|
16
|
|
|
|
|
30
|
|
1019
|
|
|
|
|
|
|
} |
1020
|
|
|
|
|
|
|
} # end for all parts |
1021
|
|
|
|
|
|
|
|
1022
|
65
|
|
|
|
|
192
|
@rc; # return all created nodes |
1023
|
|
|
|
|
|
|
} |
1024
|
|
|
|
|
|
|
|
1025
|
|
|
|
|
|
|
sub _new_node |
1026
|
|
|
|
|
|
|
{ |
1027
|
|
|
|
|
|
|
# Create a new node unless it doesn't already exist. If the group stack |
1028
|
|
|
|
|
|
|
# contains entries, the new node appears first in this/these group(s), so |
1029
|
|
|
|
|
|
|
# add it to these groups. If the newly created node contains "|", we auto |
1030
|
|
|
|
|
|
|
# split it up into several nodes and cluster these together. |
1031
|
1900
|
|
|
1900
|
|
2230
|
my ($self, $graph, $name, $group_stack, $att, $stack) = @_; |
1032
|
|
|
|
|
|
|
|
1033
|
1900
|
50
|
|
|
|
2970
|
print STDERR "# Parser: new node '$name'\n" if $graph->{debug}; |
1034
|
|
|
|
|
|
|
|
1035
|
1900
|
|
|
|
|
2844
|
$name = $self->_unquote($name, 'no_collapse'); |
1036
|
|
|
|
|
|
|
|
1037
|
1900
|
|
|
|
|
1523
|
my $autosplit; |
1038
|
1900
|
|
|
|
|
1616
|
my $uc = $self->{use_class}; |
1039
|
|
|
|
|
|
|
|
1040
|
1900
|
|
|
|
|
1945
|
my @rc = (); |
1041
|
|
|
|
|
|
|
|
1042
|
1900
|
100
|
100
|
|
|
8847
|
if ($name =~ /^\s*\z/) |
|
|
100
|
|
|
|
|
|
1043
|
|
|
|
|
|
|
{ |
1044
|
22
|
50
|
|
|
|
47
|
print STDERR "# Parser: Creating anon node\n" if $graph->{debug}; |
1045
|
|
|
|
|
|
|
# create a new anon node and add it to the graph |
1046
|
22
|
|
|
|
|
38
|
my $class = $uc->{node} . '::Anon'; |
1047
|
22
|
|
|
|
|
100
|
my $node = $class->new(); |
1048
|
22
|
|
|
|
|
53
|
@rc = ( $graph->add_node($node) ); |
1049
|
|
|
|
|
|
|
} |
1050
|
|
|
|
|
|
|
# nodes to be autosplit will be done in a sep. pass for Graphviz |
1051
|
|
|
|
|
|
|
elsif ((ref($self) eq 'Graph::Easy::Parser') && $name =~ /[^\\]\|/) |
1052
|
|
|
|
|
|
|
{ |
1053
|
50
|
|
|
|
|
56
|
$autosplit = 1; |
1054
|
50
|
|
|
|
|
105
|
@rc = $self->_autosplit_node($graph, $name, $att); |
1055
|
|
|
|
|
|
|
} |
1056
|
|
|
|
|
|
|
else |
1057
|
|
|
|
|
|
|
{ |
1058
|
|
|
|
|
|
|
# strip trailing and leading spaces |
1059
|
1828
|
|
|
|
|
4335
|
$name =~ s/\s+\z//; |
1060
|
1828
|
|
|
|
|
2939
|
$name =~ s/^\s+//; |
1061
|
|
|
|
|
|
|
|
1062
|
|
|
|
|
|
|
# collapse multiple spaces |
1063
|
1828
|
|
|
|
|
1957
|
$name =~ s/\s+/ /g; |
1064
|
|
|
|
|
|
|
|
1065
|
|
|
|
|
|
|
# unquote \| |
1066
|
1828
|
|
|
|
|
1598
|
$name =~ s/\\\|/\|/g; |
1067
|
|
|
|
|
|
|
|
1068
|
1828
|
50
|
|
|
|
2887
|
if ($self->{debug}) |
1069
|
|
|
|
|
|
|
{ |
1070
|
0
|
0
|
|
|
|
0
|
if (!$graph->node($name)) |
1071
|
|
|
|
|
|
|
{ |
1072
|
0
|
|
|
|
|
0
|
print STDERR "# Parser: Creating normal node from name '$name'.\n"; |
1073
|
|
|
|
|
|
|
} |
1074
|
|
|
|
|
|
|
else |
1075
|
|
|
|
|
|
|
{ |
1076
|
0
|
|
|
|
|
0
|
print STDERR "# Parser: Found node '$name' already in graph.\n"; |
1077
|
|
|
|
|
|
|
} |
1078
|
|
|
|
|
|
|
} |
1079
|
1828
|
|
|
|
|
3219
|
@rc = ( $self->_add_node($graph, $name) ); # add to graph, unless exists |
1080
|
|
|
|
|
|
|
} |
1081
|
|
|
|
|
|
|
|
1082
|
1900
|
50
|
66
|
|
|
3578
|
$self->parse_error(5) if exists $att->{basename} && !$autosplit; |
1083
|
|
|
|
|
|
|
|
1084
|
1900
|
|
|
|
|
1546
|
my $b = $att->{basename}; |
1085
|
1900
|
|
|
|
|
1533
|
delete $att->{basename}; |
1086
|
|
|
|
|
|
|
|
1087
|
|
|
|
|
|
|
# on a node list "[A],[B] { ... }" set attributes on all nodes |
1088
|
|
|
|
|
|
|
# encountered so far, too: |
1089
|
1900
|
100
|
|
|
|
2542
|
if (defined $stack) |
1090
|
|
|
|
|
|
|
{ |
1091
|
94
|
|
|
|
|
109
|
for my $node (@$stack) |
1092
|
|
|
|
|
|
|
{ |
1093
|
145
|
|
|
|
|
251
|
$node->set_attributes ($att, 0); |
1094
|
|
|
|
|
|
|
} |
1095
|
|
|
|
|
|
|
} |
1096
|
1900
|
|
|
|
|
1335
|
my $index = 0; |
1097
|
1900
|
|
|
|
|
1869
|
my $group = $self->{group_stack}->[-1]; |
1098
|
|
|
|
|
|
|
|
1099
|
1900
|
|
|
|
|
1976
|
for my $node (@rc) |
1100
|
|
|
|
|
|
|
{ |
1101
|
1993
|
100
|
|
|
|
2741
|
$node->add_to_group($group) if $group; |
1102
|
1993
|
|
|
|
|
3757
|
$node->set_attributes ($att, $index); |
1103
|
1993
|
|
|
|
|
1888
|
$index++; |
1104
|
|
|
|
|
|
|
} |
1105
|
|
|
|
|
|
|
|
1106
|
1900
|
100
|
|
|
|
2694
|
$att->{basename} = $b if defined $b; |
1107
|
|
|
|
|
|
|
|
1108
|
|
|
|
|
|
|
# return list of created nodes (usually one, but more for "A|B") |
1109
|
1900
|
|
|
|
|
3768
|
@rc; |
1110
|
|
|
|
|
|
|
} |
1111
|
|
|
|
|
|
|
|
1112
|
|
|
|
|
|
|
sub _match_comma |
1113
|
|
|
|
|
|
|
{ |
1114
|
|
|
|
|
|
|
# return a regexp that matches something like " , " like in: |
1115
|
|
|
|
|
|
|
# "[ Bonn ], [ Berlin ] => [ Hamburg ]" |
1116
|
324
|
|
|
324
|
|
488
|
qr/\s*,\s*/; |
1117
|
|
|
|
|
|
|
} |
1118
|
|
|
|
|
|
|
|
1119
|
|
|
|
|
|
|
sub _match_comment |
1120
|
|
|
|
|
|
|
{ |
1121
|
|
|
|
|
|
|
# match the start of a comment |
1122
|
324
|
|
|
324
|
|
636
|
qr/(^|[^\\])#/; |
1123
|
|
|
|
|
|
|
} |
1124
|
|
|
|
|
|
|
|
1125
|
|
|
|
|
|
|
sub _match_commented_line |
1126
|
|
|
|
|
|
|
{ |
1127
|
|
|
|
|
|
|
# match empty lines or a completely commented out line |
1128
|
426
|
|
|
426
|
|
1067
|
qr/^\s*(#|\z)/; |
1129
|
|
|
|
|
|
|
} |
1130
|
|
|
|
|
|
|
|
1131
|
|
|
|
|
|
|
sub _match_attributes |
1132
|
|
|
|
|
|
|
{ |
1133
|
|
|
|
|
|
|
# return a regexp that matches something like " { color: red; }" and returns |
1134
|
|
|
|
|
|
|
# the inner text without the {} |
1135
|
324
|
|
|
324
|
|
516
|
qr/\s*\{\s*([^\}]+?)\s*\}/; |
1136
|
|
|
|
|
|
|
} |
1137
|
|
|
|
|
|
|
|
1138
|
|
|
|
|
|
|
sub _match_optional_attributes |
1139
|
|
|
|
|
|
|
{ |
1140
|
|
|
|
|
|
|
# return a regexp that matches something like " { color: red; }" and returns |
1141
|
|
|
|
|
|
|
# the inner text with the {} |
1142
|
649
|
|
|
649
|
|
1002
|
qr/(\s*\{[^\}]+?\})?/; |
1143
|
|
|
|
|
|
|
} |
1144
|
|
|
|
|
|
|
|
1145
|
|
|
|
|
|
|
sub _match_node |
1146
|
|
|
|
|
|
|
{ |
1147
|
|
|
|
|
|
|
# return a regexp that matches something like " [ bonn ]" and returns |
1148
|
|
|
|
|
|
|
# the inner text without the [] (might leave some spaces) |
1149
|
|
|
|
|
|
|
|
1150
|
326
|
|
|
326
|
|
605
|
qr/\s*\[ # '[' start of the node |
1151
|
|
|
|
|
|
|
( |
1152
|
|
|
|
|
|
|
(?: # non-capturing group |
1153
|
|
|
|
|
|
|
\\. # either '\]' or '\N' etc. |
1154
|
|
|
|
|
|
|
| # or |
1155
|
|
|
|
|
|
|
[^\]\\] # not ']' and not '\' |
1156
|
|
|
|
|
|
|
)* # 0 times for '[]' |
1157
|
|
|
|
|
|
|
) |
1158
|
|
|
|
|
|
|
\]/x; # followed by ']' |
1159
|
|
|
|
|
|
|
} |
1160
|
|
|
|
|
|
|
|
1161
|
|
|
|
|
|
|
sub _match_class_selector |
1162
|
|
|
|
|
|
|
{ |
1163
|
324
|
|
|
324
|
|
478
|
my $class = qr/(?:\.\w+|graph|(?:edge|group|node)(?:\.\w+)?)/; |
1164
|
324
|
|
|
|
|
2048
|
qr/($class(?:\s*,\s*$class)*)/; |
1165
|
|
|
|
|
|
|
} |
1166
|
|
|
|
|
|
|
|
1167
|
|
|
|
|
|
|
sub _match_single_attribute |
1168
|
|
|
|
|
|
|
{ |
1169
|
405
|
|
|
405
|
|
794
|
qr/\s*([^:]+?)\s*:\s*("(?:\\"|[^"])+"|(?:\\;|[^;])+?)(?:\s*;\s*|\s*\z)/; # "name: value" |
1170
|
|
|
|
|
|
|
} |
1171
|
|
|
|
|
|
|
|
1172
|
|
|
|
|
|
|
sub _match_group_start |
1173
|
|
|
|
|
|
|
{ |
1174
|
|
|
|
|
|
|
# Return a regexp that matches something like " ( group [" and returns |
1175
|
|
|
|
|
|
|
# the text between "(" and "[". Also matches empty groups like "( group )" |
1176
|
|
|
|
|
|
|
# or even "()": |
1177
|
648
|
|
|
648
|
|
1187
|
qr/\s*\(\s*([^\[\)\(]*?)\s*([\[\)\(])/; |
1178
|
|
|
|
|
|
|
} |
1179
|
|
|
|
|
|
|
|
1180
|
|
|
|
|
|
|
sub _match_group_end |
1181
|
|
|
|
|
|
|
{ |
1182
|
|
|
|
|
|
|
# return a regexp that matches something like " )". |
1183
|
324
|
|
|
324
|
|
663
|
qr/\s*\)\s*/; |
1184
|
|
|
|
|
|
|
} |
1185
|
|
|
|
|
|
|
|
1186
|
|
|
|
|
|
|
sub _match_edge |
1187
|
|
|
|
|
|
|
{ |
1188
|
|
|
|
|
|
|
# Matches all possible edge variants like: |
1189
|
|
|
|
|
|
|
# -->, ---->, ==> etc |
1190
|
|
|
|
|
|
|
# <-->, <---->, <==>, <..> etc |
1191
|
|
|
|
|
|
|
# <-- label -->, <.- label .-> etc |
1192
|
|
|
|
|
|
|
# -- label -->, .- label .-> etc |
1193
|
|
|
|
|
|
|
|
1194
|
|
|
|
|
|
|
# "- " must come before "-"! |
1195
|
|
|
|
|
|
|
# likewise, "..-" must come before ".-" must come before "." |
1196
|
|
|
|
|
|
|
|
1197
|
|
|
|
|
|
|
# XXX TODO: convert the first group into a non-matching group |
1198
|
|
|
|
|
|
|
|
1199
|
325
|
|
|
325
|
|
591
|
qr/\s* |
1200
|
|
|
|
|
|
|
( # egde without label ("-->") |
1201
|
|
|
|
|
|
|
() # optional left "<" |
1202
|
|
|
|
|
|
|
(=\s|=|-\s|-|\.\.-|\.-|\.|~)+> # pattern (style) of edge |
1203
|
|
|
|
|
|
|
| # edge with label ("-- label -->") |
1204
|
|
|
|
|
|
|
() # optional left "<" |
1205
|
|
|
|
|
|
|
((=\s|=|-\s|-|\.\.-|\.-|\.|~)+) # pattern (style) of edge |
1206
|
|
|
|
|
|
|
\s+ # followed by at least a space |
1207
|
|
|
|
|
|
|
((?:\\.|[^>\[\{])*?) # either \\, \[ etc, or not ">", "[", "{" |
1208
|
|
|
|
|
|
|
(\s+\5)> # a space and pattern before ">" |
1209
|
|
|
|
|
|
|
|
1210
|
|
|
|
|
|
|
# inserting this needs mucking with all the code that access $5 etc |
1211
|
|
|
|
|
|
|
# | # undirected edge (without arrows, but with label) |
1212
|
|
|
|
|
|
|
# ((=\s|=|-\s|-|\.\.-|\.-|\.|~)+) # pattern (style) of edge |
1213
|
|
|
|
|
|
|
# \s+ # followed by at least a space |
1214
|
|
|
|
|
|
|
# ((?:\\.|[^>\[\{])*?) # either \\, \[ etc, or not ">", "[", "{" |
1215
|
|
|
|
|
|
|
# (\s+\10) # a space and pattern |
1216
|
|
|
|
|
|
|
|
1217
|
|
|
|
|
|
|
| # undirected edge (without arrows and label) |
1218
|
|
|
|
|
|
|
(\.\.-|\.-)+ # pattern (style) of edge (at least once) |
1219
|
|
|
|
|
|
|
| |
1220
|
|
|
|
|
|
|
(=\s|=|-\s|-|\.|~){2,} # these at least two times |
1221
|
|
|
|
|
|
|
) |
1222
|
|
|
|
|
|
|
/x; |
1223
|
|
|
|
|
|
|
} |
1224
|
|
|
|
|
|
|
|
1225
|
|
|
|
|
|
|
sub _clean_attributes |
1226
|
|
|
|
|
|
|
{ |
1227
|
2578
|
|
|
2578
|
|
2054
|
my ($self,$text) = @_; |
1228
|
|
|
|
|
|
|
|
1229
|
2578
|
|
|
|
|
2542
|
$text =~ s/^\s*\{\s*//; # remove left-over "{" and spaces |
1230
|
2578
|
|
|
|
|
2774
|
$text =~ s/\s*\}\s*\z//; # remove left-over "}" and spaces |
1231
|
|
|
|
|
|
|
|
1232
|
2578
|
|
|
|
|
3289
|
$text; |
1233
|
|
|
|
|
|
|
} |
1234
|
|
|
|
|
|
|
|
1235
|
|
|
|
|
|
|
sub _parse_attributes |
1236
|
|
|
|
|
|
|
{ |
1237
|
|
|
|
|
|
|
# Takes a text like "attribute: value; attribute2 : value2;" and |
1238
|
|
|
|
|
|
|
# returns a hash with the attributes. $class defaults to 'node'. |
1239
|
|
|
|
|
|
|
# In list context, also returns a flag that is maxlevel-1 when one |
1240
|
|
|
|
|
|
|
# of the attributes was a multiple one (aka 2 for "red|green", 1 for "red"); |
1241
|
2999
|
|
|
2999
|
|
3414
|
my ($self, $text, $object, $no_multiples) = @_; |
1242
|
|
|
|
|
|
|
|
1243
|
2999
|
|
|
|
|
2246
|
my $class = $object; |
1244
|
2999
|
100
|
|
|
|
4033
|
$class = $object->{class} if ref($object); |
1245
|
2999
|
100
|
|
|
|
4707
|
$class = 'node' unless defined $class; |
1246
|
2999
|
|
|
|
|
2791
|
$class =~ s/\..*//; # remove subclass |
1247
|
|
|
|
|
|
|
|
1248
|
2999
|
|
|
|
|
1891
|
my $out; |
1249
|
2999
|
|
|
|
|
2647
|
my $att = {}; |
1250
|
2999
|
|
|
|
|
2187
|
my $multiples = 0; |
1251
|
|
|
|
|
|
|
|
1252
|
2999
|
|
|
|
|
3897
|
$text = $self->_clean_attributes($text); |
1253
|
2999
|
|
|
|
|
2652
|
my $qr_att = $self->{_match_single_attribute}; |
1254
|
2999
|
100
|
|
|
|
1871
|
my $qr_cmt; $qr_cmt = $self->_match_multi_line_comment() |
|
2999
|
|
|
|
|
8042
|
|
1255
|
|
|
|
|
|
|
if $self->can('_match_multi_line_comment'); |
1256
|
2999
|
100
|
|
|
|
1866
|
my $qr_satt; $qr_satt = $self->_match_special_attribute() |
|
2999
|
|
|
|
|
6061
|
|
1257
|
|
|
|
|
|
|
if $self->can('_match_special_attribute'); |
1258
|
|
|
|
|
|
|
|
1259
|
2999
|
100
|
|
|
|
9192
|
return {} if $text =~ /^\s*\z/; |
1260
|
|
|
|
|
|
|
|
1261
|
666
|
50
|
|
|
|
1141
|
print STDERR "attr parsing: matching\n '$text'\n against $qr_att\n" if $self->{debug} > 3; |
1262
|
|
|
|
|
|
|
|
1263
|
666
|
|
|
|
|
1189
|
while ($text ne '') |
1264
|
|
|
|
|
|
|
{ |
1265
|
877
|
50
|
|
|
|
1332
|
print STDERR "attr parsing: matching '$text'\n" if $self->{debug} > 3; |
1266
|
|
|
|
|
|
|
|
1267
|
|
|
|
|
|
|
# remove a possible comment |
1268
|
877
|
100
|
|
|
|
1685
|
$text =~ s/^$qr_cmt//g if $qr_cmt; |
1269
|
|
|
|
|
|
|
|
1270
|
|
|
|
|
|
|
# if the last part was a comment, we end up with an empty text here: |
1271
|
877
|
100
|
|
|
|
1580
|
last if $text =~ /^\s*\z/; |
1272
|
|
|
|
|
|
|
|
1273
|
|
|
|
|
|
|
# match and remove "name: value" |
1274
|
876
|
|
100
|
|
|
7490
|
my $done = ($text =~ s/^$qr_att//) || 0; |
1275
|
|
|
|
|
|
|
|
1276
|
|
|
|
|
|
|
# match and remove "name" if "name: value;" didn't match |
1277
|
876
|
100
|
100
|
|
|
2059
|
$done++ if $done == 0 && $qr_satt && ($text =~ s/^$qr_satt//); |
|
|
|
66
|
|
|
|
|
1278
|
|
|
|
|
|
|
|
1279
|
876
|
100
|
|
|
|
1101
|
return $self->error ("Error in attribute: '$text' doesn't look valid to me.") |
1280
|
|
|
|
|
|
|
if $done == 0; |
1281
|
|
|
|
|
|
|
|
1282
|
872
|
|
|
|
|
1032
|
my $name = $1; |
1283
|
872
|
100
|
|
|
|
863
|
my $v = $2; $v = '' unless defined $v; # for special attributes w/o value |
|
872
|
|
|
|
|
1134
|
|
1284
|
|
|
|
|
|
|
|
1285
|
|
|
|
|
|
|
# unquote and store |
1286
|
872
|
|
|
|
|
1401
|
$out->{$name} = $self->_unquote_attribute($name,$v); |
1287
|
|
|
|
|
|
|
} |
1288
|
|
|
|
|
|
|
|
1289
|
662
|
50
|
33
|
|
|
1196
|
if ($self->{debug} && $self->{debug} > 1) |
1290
|
|
|
|
|
|
|
{ |
1291
|
0
|
|
|
|
|
0
|
require Data::Dumper; |
1292
|
0
|
|
|
|
|
0
|
print STDERR "# ", join (" ", caller),"\n"; |
1293
|
0
|
|
|
|
|
0
|
print STDERR "# Parsed attributes into:\n", Data::Dumper::Dumper($out),"\n"; |
1294
|
|
|
|
|
|
|
} |
1295
|
|
|
|
|
|
|
# possible remap attributes (for parsing Graphviz) |
1296
|
662
|
100
|
|
|
|
1921
|
$out = $self->_remap_attributes($out, $object) if $self->can('_remap_attributes'); |
1297
|
|
|
|
|
|
|
|
1298
|
662
|
|
|
|
|
792
|
my $g = $self->{_graph}; |
1299
|
|
|
|
|
|
|
# check for being valid and finally create hash with name => value pairs |
1300
|
662
|
|
|
|
|
1641
|
for my $name (sort keys %$out) |
1301
|
|
|
|
|
|
|
{ |
1302
|
837
|
|
|
|
|
2420
|
my ($rc, $newname, $v) = $g->validate_attribute($name,$out->{$name},$class,$no_multiples); |
1303
|
|
|
|
|
|
|
|
1304
|
837
|
100
|
|
|
|
1490
|
$self->error($g->{error}) if defined $rc; |
1305
|
|
|
|
|
|
|
|
1306
|
837
|
100
|
|
|
|
1288
|
$multiples = scalar @$v if ref($v) eq 'ARRAY'; |
1307
|
|
|
|
|
|
|
|
1308
|
837
|
100
|
|
|
|
2152
|
$att->{$newname} = $v if defined $v; # undef => ignore attribute |
1309
|
|
|
|
|
|
|
} |
1310
|
|
|
|
|
|
|
|
1311
|
662
|
100
|
|
|
|
2132
|
return $att unless wantarray; |
1312
|
|
|
|
|
|
|
|
1313
|
41
|
|
100
|
|
|
200
|
($att, $multiples || 1); |
1314
|
|
|
|
|
|
|
} |
1315
|
|
|
|
|
|
|
|
1316
|
|
|
|
|
|
|
sub parse_error |
1317
|
|
|
|
|
|
|
{ |
1318
|
|
|
|
|
|
|
# take a msg number, plus params, and throws an exception |
1319
|
3
|
|
|
3
|
1
|
4
|
my $self = shift; |
1320
|
3
|
|
|
|
|
2
|
my $msg_nr = shift; |
1321
|
|
|
|
|
|
|
|
1322
|
|
|
|
|
|
|
# XXX TODO: should really use the msg nr mapping |
1323
|
3
|
|
|
|
|
4
|
my $msg = "Found unexpected group end"; # 0 |
1324
|
3
|
100
|
|
|
|
8
|
$msg = "Error in attribute: '##param2##' is not a valid attribute for a ##param3##" # 1 |
1325
|
|
|
|
|
|
|
if $msg_nr == 1; |
1326
|
3
|
100
|
|
|
|
5
|
$msg = "Error in attribute: '##param1##' is not a valid ##param2## for a ##param3##" |
1327
|
|
|
|
|
|
|
if $msg_nr == 2; # 2 |
1328
|
3
|
100
|
|
|
|
6
|
$msg = "Error: Found attributes, but expected group or node start" |
1329
|
|
|
|
|
|
|
if $msg_nr == 3; # 3 |
1330
|
3
|
50
|
|
|
|
5
|
$msg = "Error in attribute: multi-attribute '##param1##' not allowed here" |
1331
|
|
|
|
|
|
|
if $msg_nr == 4; # 4 |
1332
|
3
|
50
|
|
|
|
4
|
$msg = "Error in attribute: basename not allowed for non-autosplit nodes" |
1333
|
|
|
|
|
|
|
if $msg_nr == 5; # 5 |
1334
|
|
|
|
|
|
|
# for graphviz parsing |
1335
|
3
|
50
|
|
|
|
6
|
$msg = "Error: Already seen graph start" |
1336
|
|
|
|
|
|
|
if $msg_nr == 6; # 6 |
1337
|
3
|
50
|
|
|
|
5
|
$msg = "Error: Expected '}', but found file end" |
1338
|
|
|
|
|
|
|
if $msg_nr == 7; # 7 |
1339
|
|
|
|
|
|
|
|
1340
|
3
|
|
|
|
|
2
|
my $i = 1; |
1341
|
3
|
|
|
|
|
5
|
foreach my $p (@_) |
1342
|
|
|
|
|
|
|
{ |
1343
|
6
|
|
|
|
|
59
|
$msg =~ s/##param$i##/$p/g; $i++; |
|
6
|
|
|
|
|
8
|
|
1344
|
|
|
|
|
|
|
} |
1345
|
|
|
|
|
|
|
|
1346
|
3
|
|
|
|
|
10
|
$self->error($msg . ' at line ' . $self->{line_nr}); |
1347
|
|
|
|
|
|
|
} |
1348
|
|
|
|
|
|
|
|
1349
|
|
|
|
|
|
|
sub _parser_cleanup |
1350
|
|
|
|
|
|
|
{ |
1351
|
|
|
|
|
|
|
# After initial parsing, do a cleanup pass. |
1352
|
324
|
|
|
324
|
|
362
|
my ($self) = @_; |
1353
|
|
|
|
|
|
|
|
1354
|
324
|
|
|
|
|
319
|
my $g = $self->{_graph}; |
1355
|
|
|
|
|
|
|
|
1356
|
324
|
|
|
|
|
888
|
for my $n (ord_values ( $g->{nodes} )) |
1357
|
|
|
|
|
|
|
{ |
1358
|
1126
|
100
|
|
|
|
1712
|
next if $n->{autosplit}; |
1359
|
1076
|
50
|
66
|
|
|
2000
|
$self->warn("Node '" . $self->_quote($n->{name}) . "' has an offset but no origin") |
1360
|
|
|
|
|
|
|
if (($n->attribute('offset') ne '0,0') && $n->attribute('origin') eq ''); |
1361
|
|
|
|
|
|
|
} |
1362
|
|
|
|
|
|
|
|
1363
|
324
|
|
|
|
|
477
|
$self; |
1364
|
|
|
|
|
|
|
} |
1365
|
|
|
|
|
|
|
|
1366
|
|
|
|
|
|
|
sub _quote |
1367
|
|
|
|
|
|
|
{ |
1368
|
|
|
|
|
|
|
# make a node name safe for error message output |
1369
|
0
|
|
|
0
|
|
|
my ($self,$n) = @_; |
1370
|
|
|
|
|
|
|
|
1371
|
0
|
|
|
|
|
|
$n =~ s/'/\\'/g; |
1372
|
|
|
|
|
|
|
|
1373
|
0
|
|
|
|
|
|
$n; |
1374
|
|
|
|
|
|
|
} |
1375
|
|
|
|
|
|
|
|
1376
|
|
|
|
|
|
|
1; |
1377
|
|
|
|
|
|
|
__END__ |