GraphViz2-Marpa-Extractor

 view release on metacpan or  search on metacpan

script/dtd4  view on Meta::CPAN

			my $sub_brace_lit = $body[$i + 2];

			my $sub = _extract_subgraph($sub_name, $sub_brace_lit, $kind);
			push @subgraphs, $sub;

			# Skip id + brace literal; outer loop will move on.
			next;
		}

		# Edge: node_id, edge_id, node_id
		if ($nm eq 'edge_id') {
			my $edge_type = $t; # directed_edge / undirected_edge
			my $left      = $body[$i - 1];
			my $right     = $body[$i + 1];

			my $from = $left->{attributes}{name};
			my ($from_port, $from_compass) = _parse_port($left->{attributes}{port}, $left->{attributes}{value});

			my $to = $right->{attributes}{name};
			my ($to_port, $to_compass) = _parse_port($right->{attributes}{port}, $right->{attributes}{value});

			# Edge attributes may be in a bracket list attached to the right node_id.
			my %edge_attr;
			if ($right->{daughters} && @{ $right->{daughters} }) {
				for my $c (@{ $right->{daughters} }) {
					next unless ($c->{name} // '') eq 'attribute';
					my $ct  = $c->{attributes}{type} || $c->{attributes}{name};
					my $val = $c->{attributes}{value};
					$edge_attr{$ct} = $val;
				}
			}

			# Merge defaults into edge attrs.
			my $merged_attr = _merge_attrs(\%edge_defaults, \%edge_attr);

			my $edge = {
				from        => $from,
				from_port   => $from_port,
				from_compass=> $from_compass,
				to          => $to,
				to_port     => $to_port,
				to_compass  => $to_compass,
				attrs       => $merged_attr,
			};

			push @edges, $edge;

			# Ensure nodes exist in nodes hash (even if no explicit node block).
			$nodes{$from} ||= { attrs => {} };
			$nodes{$to}   ||= { attrs => {} };

			next;
		}

		# Node declaration: node_id with optional bracketed attributes.
		if ($nm eq 'node_id' && $t eq 'node_id') {
			my $node_name = $n->{attributes}{name};

			# If this node_id is part of an edge (immediately before/after edge_id),
			# we still want its attributes, but we don't treat it as a standalone node block.
			my $is_edge_endpoint = 0;
			if ($i + 1 < @body && ($body[$i + 1]{name} // '') eq 'edge_id') {
				$is_edge_endpoint = 1;
			}
			if ($i > 0 && ($body[$i - 1]{name} // '') eq 'edge_id') {
				$is_edge_endpoint = 1;
			}

			my %node_attr;
			if ($n->{daughters} && @{ $n->{daughters} }) {
				for my $c (@{ $n->{daughters} }) {
					next unless ($c->{name} // '') eq 'attribute';
					my $ct  = $c->{attributes}{type} || $c->{attributes}{name};
					my $val = $c->{attributes}{value};
					$node_attr{$ct} = $val;
				}
			}

			# Merge defaults into node attrs.
			my $merged_attr = _merge_attrs(\%node_defaults, \%node_attr);

			# Merge into existing node entry if present.
			if (exists $nodes{$node_name}) {
				$nodes{$node_name}{attrs} = _merge_attrs($nodes{$node_name}{attrs}, $merged_attr);
			}
			else {
				$nodes{$node_name} = { attrs => $merged_attr };
			}

			next;
		}
	}

	# Build adjacency (respecting kind: digraph vs graph).
	my $adjacency = _build_adjacency(\@edges, $kind);

	my $graph = {
		name          => $name,
		kind          => $kind,
		attrs         => \%attrs,
		node_defaults => \%node_defaults,
		edge_defaults => \%edge_defaults,
		nodes         => \%nodes,
		edges         => \@edges,
		subgraphs     => \@subgraphs,
		adjacency     => $adjacency,
	};

	return $graph;
}

sub _extract_subgraph {
	my ($name, $brace_lit, $kind) = @_;

	my @body = @{ $brace_lit->{daughters} || [] };

	my %attrs;
	my %node_defaults;
	my %edge_defaults;
	my %nodes;
	my @edges;
	my @subgraphs;

	for (my $i = 0; $i < @body; $i++) {
		my $n  = $body[$i];
		my $nm = $n->{name} // '';



( run in 0.895 second using v1.01-cache-2.11-cpan-39bf76dae61 )