I have been following your example to get the node attributes but it seems that its not working for cluster. Here is an example of dot file I'd like to support
Unable to get the label attribute from the cluster #4
Hi,
I'm not sure I understand exactly what output you expect in the example you provide (which node attribute should appear, but doesn't).
However, your example helped me identify a bug, where keywords (e.g. "subgraph") where parsed as identifiers (notice that, strictly speaking, the spec is a bit... informal about keywords, hence the error). Could you try with the latest git version and let me know if that fixes your issue or not?
The parser is crashing with:
called `Result::unwrap()` on an `Err` value: PestParseError(Error { variant: ParsingError { positives: [attr_list], negatives: [] }, location: Pos(89), line_col: Pos((6, 7)), path: None, line: " nodesep=.1;", continued_line: None, parse_attempts: None })
use dot_parser::*;
use std::env;
fn main() {
let args: Vec<String> = env::args().collect();
// Check if a filename was provided
if args.len() < 2 {
eprintln!("Usage: {} <filename>", args[0]);
std::process::exit(1);
}
let filename = &args[1];
let graph = canonical::Graph::from(ast::Graph::from_file(filename).unwrap());
println!("Nodes");
for node in graph.nodes.set.values() {
println!("node id: {}", node.id);
for (attr, val) in &node.attr {
println!("({}, {})", attr, val);
}
}
println!("Edges");
for edge in graph.edges.set {
println!("{} -> {}", edge.from, edge.to);
for (attr, val) in &edge.attr {
println!("({}, {})", attr, val);
}
}
}
Mmhh... It seems to work on my side. I have the attached output.
Could you attach your Cargo.toml and run cargo tree (https://doc.rust-lang.org/cargo/commands/cargo-tree.html) to check whether this is due to a package version issue?
dot-test v0.1.0 (/DEV/PERSO/GPS/dot-test)
└── dot-parser v0.3.2 (https://codeberg.org/bromind/dot-parser.git#e9efd4af)
├── either v1.13.0
├── pest v2.7.14
│ ├── memchr v2.7.4
│ ├── thiserror v1.0.65
│ │ └── thiserror-impl v1.0.65 (proc-macro)
│ │ ├── proc-macro2 v1.0.89
│ │ │ └── unicode-ident v1.0.13
│ │ ├── quote v1.0.37
│ │ │ └── proc-macro2 v1.0.89 (*)
│ │ └── syn v2.0.82
│ │ ├── proc-macro2 v1.0.89 (*)
│ │ ├── quote v1.0.37 (*)
│ │ └── unicode-ident v1.0.13
│ └── ucd-trie v0.1.7
└── pest_derive v2.7.14 (proc-macro)
├── pest v2.7.14 (*)
└── pest_generator v2.7.14
├── pest v2.7.14 (*)
├── pest_meta v2.7.14
│ ├── once_cell v1.20.2
│ └── pest v2.7.14 (*)
│ [build-dependencies]
│ └── sha2 v0.10.8
│ ├── cfg-if v1.0.0
│ ├── cpufeatures v0.2.14
│ └── digest v0.10.7
│ ├── block-buffer v0.10.4
│ │ └── generic-array v0.14.7
│ │ └── typenum v1.17.0
│ │ [build-dependencies]
│ │ └── version_check v0.9.5
│ └── crypto-common v0.1.6
│ ├── generic-array v0.14.7 (*)
│ └── typenum v1.17.0
├── proc-macro2 v1.0.89 (*)
├── quote v1.0.37 (*)
└── syn v2.0.82 (*)
Hi, I can reproduce the error now. I'll have a look at it.
oh great ! What was the difference ?
I introduced this bug in #70d66ffc4d, which should be fixed in #73cd9df5d5 and following . Could you confirm it works now on your side?
oh great ! What was the difference ?
So, in #70d66ffc4d , I prevented keywords to be parsed as identifiers (note: strictly speaking, nothing in the spec prevents it, but it seems to be the intention of the authors). However, I did that in a quick and dirty way, simply checking that an identifier did not start with a keyword:
keyword = _{"node" | "edge" | "graph" | "digraph" | "subgraph" | "strict" }
ident1 = _{ !keyword ~ (ASCII_ALPHA | "_") ~ (ASCII_ALPHANUMERIC | "_")* }
However, this also prevent identifiers that begin with a keyword, but continue with something else (e.g. nodesep is forbidden, as it begins with node which is a keyword). This is why your graph was rejected.
To address that, I simply prevent keywords followed by a whitespace, instead of keywords alone:
ident1 = _{ !(keyword~WHITESPACE) ~ (ASCII_ALPHA | "_") ~ (ASCII_ALPHANUMERIC | "_")* }
not crashing anymore but there are some missing nodes I was having before:
node id: cluster_autovideosink0_actual_sink_xvimage_0x5d76d66b3730
node id: cluster_autovideosink0_0x5d76d66a1050
node id: cluster_videoconvert0_0x5d76d669e160_sink
node id: subgraph
node id: cluster_toto_0x5d76d66b20e0
node id: cluster_videoconvert0_0x5d76d669e160_src
...
These nodes would help me to reconstuct the graph for GstPipelineStudio
Revision: #3aa8535937
In your example, those identifiers are not node identifiers but subgraph identifiers (note that, in the extract you show subgraph itself is not an identifier, but a keyword that was formerly improperly parsed), c.f. https://www.graphviz.org/doc/info/lang.html#subgraphs-and-clusters.
Before #70d66ffc4d, there was an error in the parser, and those appeared as nodes, which is incorrect. This is why they used to appear in the list of nodes. While converting an "AST" graph (i.e. the structure that precisely match the grammar) into an equivalent "cannonical" graph (i.e. a graph easier to work with), subgraphs are flatten according to this part of the spec :
A -> {B C}is equivalent to
A -> B A -> C
This is why subgraph identifiers are lost while flattening: subgraph are removed.
If you really want to work with the subgraphs, you can work with "AST" graphs instead of converting them into "canonical" graphs:
let graph = ast::Graph::from_file(filename).unwrap();
But this can make other things more difficult: e.g. iterating over all nodes might be tricky, because it depends whether you want to include nested nodes or not.
N.B., if that matters: One thing that is currently missing and that I'm planning to add is to preserve attributes while flattening. As of today, the graph:
graph {
A -> subgraph G { node [ attr=val ] B C}
}
is flattened into
graph {
A -> B
A -> C
}
and the node attribute attr=val is lost. I'm planning to add it to the nodes it affect, so that the graph is flattened as:
graph {
B[attr=val]
C[attr=val]
A -> B
A -> C
}
I'm missing what you are trying to do exactly, but if you explain a bit more the big picture of your project, maybe I can add functions to help you/make your life easier.
I'm currently working on a GUI for GStreamer named GstPipelineStudio, and I'd like a way to read dot files to reproduce a graph such as described in this picture.
What would I need is node such as GstVideoTestSrc or subgraph with all its attributes in order to be able to reinstanciate the element according to this attribute name (VideoTestSrc) and then I would need have the connection between nodes src -> sink.
Would it be possible to have as in revision 3aa8535937 the attributes of the subgraph.
Hope its clearer
I understand a bit better. I see that you have nested clusters (e.g. for GstVideoTestSrc, you have the outer red box, which is a subgraph with ID cluster_toto_0x5d76d66b20e0, which itself contains src, a subgraph with ID cluster_toto_0x5d76d66b20e0_src).
Are you trying to access only the attributes of the outer subgraph (cluster_toto_0x5d76d66b20e0) or both the outer and the inner one (cluster_toto_0x5d76d66b20e0_src and cluster_toto_0x5d76d66b20e0)?
I'm asking because I can very easily add a method get_subgraphs on ast::graph, that would be similar to get_node_ids, but would return the subgraphs instead of the node ids. The signature would be something like: fn get_subgraphs(self) -> HashSet(Subgraph) (c.f. Subgraph). Would that fit your needs?
I would need to access the attributes for the subgraph cluster_toto_0x5d76d66b20e0 for example
And also the link between the subgraph such as toto_0x5d76d66b20e0_src_0x5d76d66961c0 -> videoconvert0_0x5d76d669e160_sink_0x5d76d6696410
Using the ast::Graph, it is possible to recover the subgraphs:
use dot_parser::*;
use std::env;
fn main() {
let args: Vec<String> = env::args().collect();
// Check if a filename was provided
if args.len() < 2 {
eprintln!("Usage: {} <filename>", args[0]);
std::process::exit(1);
}
let filename = &args[1];
let graph = ast::Graph::from_file(filename).unwrap();
println!("Nodes");
for subgraph in graph.stmts.into_iter().filter_map(|stmt| {
if let ast::Stmt::Subgraph(g) = stmt {
Some(g)
} else {
None
}
}) {
println!("subgraph id: {}", subgraph.id.unwrap_or(String::from("No ID")));
for attr in subgraph
.stmts
.into_iter()
.filter_map(|stmt| stmt.get_attr())
{
println!("{:#?}", attr); // example of attrs
}
}
}
which yields:
Nodes
subgraph id: cluster_autovideosink0_0x5d76d66a1050
subgraph id: cluster_videoconvert0_0x5d76d669e160
subgraph id: cluster_toto_0x5d76d66b20e0
It is quite unconvenient though, because edges are more difficult to work with. I'm considering adding an intermediate representation between ast::Graph and canonical::Graph, where edges are flattened, statement separated depending on their kind, but preserving subgraphs. I'll need a bit of time to implement it, as it will change deeply the conversion between ast::Graphs and canonical::Graph.
Hi,
I'm starting working on your request, would adding a structure like the following be fine for your usecase (with, of course, conversion from ast::Graph)?
pubstruct Graph<A>{pubstrict: bool,pubis_digraph: bool,pubname: Option<String>,pubattr: Vec<AttrStmt<A>>,pubnodes: NodeSet<A>,pubedges: EdgeSet<A>,pubsubgraphs: SubgraphSet<A>,pubideqs: Vec<IDEq>,}It is more or less the same structure as canonical::Graph, with an extra field for subgraphs, which would not be expended.
yeah sounds interesting indeed. Are we going to have Attributes for subgraphs ?
(This isn't done yet, so take what I say below with a grain of salt.)
What I have in mind right now for the set of subgraphs would be something like HashSet<ast::Subgraph> (see here ); or with a variant of ast::Subgraph that separates the different kinds of attributes, e.g. something like:
pubstruct Subgraph<A>{pubid: Option<String>,pubattr: Vec<AttrStmt<A>>,pubnodes: NodeSet<A>,pubedges: EdgeSet<A>,pubsubgraphs: SubgraphSet<A>,pubideqs: Vec<IDEq>,}so... yes, one should be able to get attributes fairly easily
I've been looking at implementing the thing, which turns out to be trickier that I though initially. Suppose you have the following graph:
digraph A {
B -> { C D } -> { E F }
}
The point of adding a new "cluster graph" would be to cut the edges into individual ones, such as in canonical::Graph. However, here, that would mean duplicating the whole subgraph:
digraph A {
B -> { C D }
{ C D } -> { E F }
}
I'm not sure doing this duplication is risk-free. Considering our previous exchanges, I would consider adding a method to extract the subgraphs from an ast::graph, something like what I presented here. Would that be ok, instead of adding a specific graph type ?
No due date set.
No dependencies set.
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?