Packages

Comprehensive graph file format I/O for the yog graph library - Support for GraphML, GDF, JSON (D3, Cytoscape, VisJs), TGF, LEDA, Pajek, Adjacency List/Matrix, and Matrix Market

Current section

Files

Jump to
yog_io src yog_io@gdf.erl
Raw

src/yog_io@gdf.erl

-module(yog_io@gdf).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/yog_io/gdf.gleam").
-export([default_options/0, serialize_with/4, serialize/1, serialize_weighted/1, write/2, write_with/5, deserialize_with/3, deserialize/1, read/1, read_with/3]).
-export_type([gdf_options/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(
" GDF (GUESS Graph Format) serialization support.\n"
"\n"
" Provides functions to serialize and deserialize graphs in GDF format,\n"
" a simple text-based format used by Gephi and other graph visualization tools.\n"
" GDF uses a column-based format similar to CSV with separate sections for nodes and edges.\n"
"\n"
" ## Format Overview\n"
"\n"
" GDF files consist of two sections:\n"
" - **nodedef>** - Defines node columns and data\n"
" - **edgedef>** - Defines edge columns and data\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" import yog/model.{Directed}\n"
" import yog_io/gdf\n"
"\n"
" // Create a simple graph\n"
" let graph =\n"
" model.new(Directed)\n"
" |> model.add_node(1, \"Alice\")\n"
" |> model.add_node(2, \"Bob\")\n"
"\n"
" let assert Ok(graph) = model.add_edge(graph, from: 1, to: 2, with: \"5\")\n"
"\n"
" // Serialize to GDF\n"
" let gdf_string = gdf.serialize(graph)\n"
"\n"
" // Write to file\n"
" let assert Ok(Nil) = gdf.write(\"graph.gdf\", graph)\n"
" ```\n"
"\n"
" ## Output Format\n"
"\n"
" ```\n"
" nodedef>name VARCHAR,label VARCHAR\n"
" 1,Alice\n"
" 2,Bob\n"
" edgedef>node1 VARCHAR,node2 VARCHAR,directed BOOLEAN,weight VARCHAR\n"
" 1,2,true,5\n"
" ```\n"
"\n"
" ## References\n"
"\n"
" - [GDF Format Specification](https://gephi.org/users/supported-graph-formats/gdf-format/)\n"
" - [GUESS Visualization Tool](https://graphexploration.cond.org/)\n"
).
-type gdf_options() :: {gdf_options,
binary(),
boolean(),
gleam@option:option(boolean())}.
-file("src/yog_io/gdf.gleam", 76).
?DOC(" Default GDF serialization options.\n").
-spec default_options() -> gdf_options().
default_options() ->
{gdf_options, <<","/utf8>>, true, none}.
-file("src/yog_io/gdf.gleam", 680).
-spec find_line_index_loop(list(binary()), binary(), integer()) -> {ok,
integer()} |
{error, nil}.
find_line_index_loop(Lines, Prefix, Idx) ->
case Lines of
[] ->
{error, nil};
[First | Rest] ->
Trimmed = gleam@string:trim(First),
case gleam_stdlib:string_starts_with(Trimmed, Prefix) of
true ->
{ok, Idx};
false ->
find_line_index_loop(Rest, Prefix, Idx + 1)
end
end.
-file("src/yog_io/gdf.gleam", 676).
-spec find_line_index(list(binary()), binary()) -> {ok, integer()} |
{error, nil}.
find_line_index(Lines, Prefix) ->
find_line_index_loop(Lines, Prefix, 0).
-file("src/yog_io/gdf.gleam", 697).
-spec parse_header(binary()) -> list(binary()).
parse_header(Line) ->
Without_prefix = case gleam@string:split(Line, <<">"/utf8>>) of
[_, Rest] ->
Rest;
_ ->
Line
end,
_pipe = Without_prefix,
_pipe@1 = gleam@string:split(_pipe, <<","/utf8>>),
_pipe@2 = gleam@list:map(
_pipe@1,
fun(Part) -> case gleam@string:split(Part, <<" "/utf8>>) of
[First | _] ->
gleam@string:trim(First);
_ ->
gleam@string:trim(Part)
end end
),
gleam@list:filter(_pipe@2, fun(Col) -> not gleam@string:is_empty(Col) end).
-file("src/yog_io/gdf.gleam", 721).
-spec parse_csv_line_loop(
list(binary()),
list(binary()),
list(binary()),
boolean()
) -> list(binary()).
parse_csv_line_loop(Chars, Current, Fields, In_quotes) ->
case Chars of
[] ->
Field = begin
_pipe = erlang:list_to_binary(lists:reverse(Current)),
gleam@string:trim(_pipe)
end,
lists:reverse([Field | Fields]);
[<<"\""/utf8>> | Rest] ->
case In_quotes of
true ->
case Rest of
[<<"\""/utf8>> | Rest2] ->
parse_csv_line_loop(
Rest2,
[<<"\""/utf8>> | Current],
Fields,
true
);
_ ->
Field@1 = erlang:list_to_binary(
lists:reverse(Current)
),
parse_csv_line_loop(
Rest,
[],
[Field@1 | Fields],
false
)
end;
false ->
parse_csv_line_loop(Rest, Current, Fields, true)
end;
[<<","/utf8>> | Rest@1] ->
case In_quotes of
true ->
parse_csv_line_loop(
Rest@1,
[<<","/utf8>> | Current],
Fields,
true
);
false ->
Field@2 = begin
_pipe@1 = erlang:list_to_binary(lists:reverse(Current)),
gleam@string:trim(_pipe@1)
end,
parse_csv_line_loop(Rest@1, [], [Field@2 | Fields], false)
end;
[C | Rest@2] ->
parse_csv_line_loop(Rest@2, [C | Current], Fields, In_quotes)
end.
-file("src/yog_io/gdf.gleam", 717).
-spec parse_csv_line(binary()) -> list(binary()).
parse_csv_line(Line) ->
parse_csv_line_loop(gleam@string:to_graphemes(Line), [], [], false).
-file("src/yog_io/gdf.gleam", 777).
-spec find_column_index_loop(list(binary()), binary(), integer()) -> {ok,
integer()} |
{error, nil}.
find_column_index_loop(Columns, Name, Idx) ->
case Columns of
[] ->
{error, nil};
[First | Rest] ->
case First =:= Name of
true ->
{ok, Idx};
false ->
find_column_index_loop(Rest, Name, Idx + 1)
end
end.
-file("src/yog_io/gdf.gleam", 773).
-spec find_column_index(list(binary()), binary()) -> {ok, integer()} |
{error, nil}.
find_column_index(Columns, Name) ->
find_column_index_loop(Columns, Name, 0).
-file("src/yog_io/gdf.gleam", 793).
-spec escape_value(binary(), binary()) -> binary().
escape_value(Separator, Value) ->
case ((gleam_stdlib:contains_string(Value, Separator) orelse gleam_stdlib:contains_string(
Value,
<<"\n"/utf8>>
))
orelse gleam_stdlib:contains_string(Value, <<"\r"/utf8>>))
orelse gleam_stdlib:contains_string(Value, <<"\""/utf8>>) of
true ->
<<<<"\""/utf8,
(gleam@string:replace(Value, <<"\""/utf8>>, <<"\"\""/utf8>>))/binary>>/binary,
"\""/utf8>>;
false ->
Value
end.
-file("src/yog_io/gdf.gleam", 133).
?DOC(
" Serializes a graph to GDF format with custom attribute mappers and options.\n"
"\n"
" This function allows you to control how node and edge data are converted\n"
" to GDF attributes, and customize the output format.\n"
"\n"
" **Time Complexity:** O(V + E)\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" import gleam/dict\n"
" import yog/model.{Directed}\n"
" import yog_io/gdf\n"
"\n"
" type Person {\n"
" Person(name: String, age: Int)\n"
" }\n"
"\n"
" type Connection {\n"
" Connection(weight: Int, relation: String)\n"
" }\n"
"\n"
" let graph =\n"
" model.new(Directed)\n"
" |> model.add_node(1, Person(\"Alice\", 30))\n"
" |> model.add_node(2, Person(\"Bob\", 25))\n"
"\n"
" let node_attrs = fn(p: Person) {\n"
" dict.from_list([#(\"label\", p.name), #(\"age\", int.to_string(p.age))])\n"
" }\n"
"\n"
" let edge_attrs = fn(c: Connection) {\n"
" dict.from_list([#(\"weight\", int.to_string(c.weight)), #(\"type\", c.relation)])\n"
" }\n"
"\n"
" let gdf = gdf.serialize_with(node_attrs, edge_attrs, gdf.default_options(), graph)\n"
" ```\n"
).
-spec serialize_with(
fun((ABOW) -> gleam@dict:dict(binary(), binary())),
fun((ABOX) -> gleam@dict:dict(binary(), binary())),
gdf_options(),
yog@model:graph(ABOW, ABOX)
) -> binary().
serialize_with(Node_attr, Edge_attr, Options, Graph) ->
Nodes_list = maps:to_list(erlang:element(3, Graph)),
Node_attr_columns = case gleam@list:first(Nodes_list) of
{ok, {_, First_node_data}} ->
maps:keys(Node_attr(First_node_data));
{error, _} ->
[]
end,
Edges_list = begin
_pipe = maps:to_list(erlang:element(4, Graph)),
gleam@list:flat_map(
_pipe,
fun(Entry) ->
{Src, Targets} = Entry,
_pipe@1 = maps:to_list(Targets),
gleam@list:map(
_pipe@1,
fun(Target_entry) ->
{Dst, Weight} = Target_entry,
{Src, Dst, Weight}
end
)
end
)
end,
Edge_attr_columns = case gleam@list:first(Edges_list) of
{ok, {_, _, First_edge_data}} ->
maps:keys(Edge_attr(First_edge_data));
{error, _} ->
[]
end,
Include_directed_col = case erlang:element(4, Options) of
{some, Flag} ->
Flag;
none ->
true
end,
Builder = gleam@string_tree:new(),
Builder@1 = gleam@string_tree:append(Builder, <<"nodedef>name"/utf8>>),
Builder@2 = case erlang:element(3, Options) of
true ->
gleam@string_tree:append(Builder@1, <<" VARCHAR"/utf8>>);
false ->
Builder@1
end,
Builder@3 = gleam@list:fold(
Node_attr_columns,
Builder@2,
fun(B, Col) ->
_pipe@2 = gleam@string_tree:append(B, erlang:element(2, Options)),
_pipe@3 = gleam@string_tree:append(_pipe@2, Col),
(fun(B2) -> case erlang:element(3, Options) of
true ->
gleam@string_tree:append(B2, <<" VARCHAR"/utf8>>);
false ->
B2
end end)(_pipe@3)
end
),
Builder@4 = gleam@string_tree:append(Builder@3, <<"\n"/utf8>>),
Builder@5 = gleam@list:fold(
Nodes_list,
Builder@4,
fun(B@1, Entry@1) ->
{Id, Data} = Entry@1,
Attrs = Node_attr(Data),
B@2 = gleam@string_tree:append(
B@1,
escape_value(
erlang:element(2, Options),
erlang:integer_to_binary(Id)
)
),
B@3 = gleam@list:fold(
Node_attr_columns,
B@2,
fun(B2@1, Col@1) ->
_pipe@4 = gleam@string_tree:append(
B2@1,
erlang:element(2, Options)
),
gleam@string_tree:append(
_pipe@4,
escape_value(
erlang:element(2, Options),
begin
_pipe@5 = gleam_stdlib:map_get(Attrs, Col@1),
gleam@result:unwrap(_pipe@5, <<""/utf8>>)
end
)
)
end
),
gleam@string_tree:append(B@3, <<"\n"/utf8>>)
end
),
Builder@6 = gleam@string_tree:append(Builder@5, <<"edgedef>node1"/utf8>>),
Builder@7 = case erlang:element(3, Options) of
true ->
gleam@string_tree:append(Builder@6, <<" VARCHAR"/utf8>>);
false ->
Builder@6
end,
Builder@8 = begin
_pipe@6 = gleam@string_tree:append(
Builder@7,
erlang:element(2, Options)
),
gleam@string_tree:append(_pipe@6, <<"node2"/utf8>>)
end,
Builder@9 = case erlang:element(3, Options) of
true ->
gleam@string_tree:append(Builder@8, <<" VARCHAR"/utf8>>);
false ->
Builder@8
end,
Builder@10 = case Include_directed_col of
true ->
B@4 = begin
_pipe@7 = gleam@string_tree:append(
Builder@9,
erlang:element(2, Options)
),
gleam@string_tree:append(_pipe@7, <<"directed"/utf8>>)
end,
case erlang:element(3, Options) of
true ->
gleam@string_tree:append(B@4, <<" BOOLEAN"/utf8>>);
false ->
B@4
end;
false ->
Builder@9
end,
Builder@11 = gleam@list:fold(
Edge_attr_columns,
Builder@10,
fun(B@5, Col@2) ->
_pipe@8 = gleam@string_tree:append(B@5, erlang:element(2, Options)),
_pipe@9 = gleam@string_tree:append(_pipe@8, Col@2),
(fun(B2@2) -> case erlang:element(3, Options) of
true ->
gleam@string_tree:append(B2@2, <<" VARCHAR"/utf8>>);
false ->
B2@2
end end)(_pipe@9)
end
),
Builder@12 = gleam@string_tree:append(Builder@11, <<"\n"/utf8>>),
Edges_to_output = case erlang:element(2, Graph) of
directed ->
Edges_list;
undirected ->
gleam@list:filter(
Edges_list,
fun(Entry@2) ->
{Src@1, Dst@1, _} = Entry@2,
Src@1 =< Dst@1
end
)
end,
Builder@13 = gleam@list:fold(
Edges_to_output,
Builder@12,
fun(B@6, Entry@3) ->
{Src@2, Dst@2, Data@1} = Entry@3,
Attrs@1 = Edge_attr(Data@1),
B@7 = begin
_pipe@10 = gleam@string_tree:append(
B@6,
escape_value(
erlang:element(2, Options),
erlang:integer_to_binary(Src@2)
)
),
_pipe@11 = gleam@string_tree:append(
_pipe@10,
erlang:element(2, Options)
),
gleam@string_tree:append(
_pipe@11,
escape_value(
erlang:element(2, Options),
erlang:integer_to_binary(Dst@2)
)
)
end,
B@8 = case Include_directed_col of
true ->
_pipe@12 = gleam@string_tree:append(
B@7,
erlang:element(2, Options)
),
gleam@string_tree:append(
_pipe@12,
case erlang:element(2, Graph) of
directed ->
<<"true"/utf8>>;
undirected ->
<<"false"/utf8>>
end
);
false ->
B@7
end,
B@9 = gleam@list:fold(
Edge_attr_columns,
B@8,
fun(B2@3, Col@3) ->
_pipe@13 = gleam@string_tree:append(
B2@3,
erlang:element(2, Options)
),
gleam@string_tree:append(
_pipe@13,
escape_value(
erlang:element(2, Options),
begin
_pipe@14 = gleam_stdlib:map_get(Attrs@1, Col@3),
gleam@result:unwrap(_pipe@14, <<""/utf8>>)
end
)
)
end
),
gleam@string_tree:append(B@9, <<"\n"/utf8>>)
end
),
unicode:characters_to_binary(Builder@13).
-file("src/yog_io/gdf.gleam", 330).
?DOC(
" Serializes a graph to GDF format where node and edge data are strings.\n"
"\n"
" This is a simplified version of `serialize_with` for graphs where\n"
" node data and edge data are already strings. The string data is used\n"
" as the \"label\" attribute for both nodes and edges.\n"
"\n"
" **Time Complexity:** O(V + E)\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" import yog/model.{Directed}\n"
" import yog_io/gdf\n"
"\n"
" let graph =\n"
" model.new(Directed)\n"
" |> model.add_node(1, \"Alice\")\n"
" |> model.add_node(2, \"Bob\")\n"
"\n"
" let assert Ok(graph) = model.add_edge(graph, from: 1, to: 2, with: \"friend\")\n"
"\n"
" let gdf = gdf.serialize(graph)\n"
" // nodedef>name VARCHAR,label VARCHAR\n"
" // 1,Alice\n"
" // 2,Bob\n"
" // edgedef>node1 VARCHAR,node2 VARCHAR,directed BOOLEAN,label VARCHAR\n"
" // 1,2,true,friend\n"
" ```\n"
).
-spec serialize(yog@model:graph(binary(), binary())) -> binary().
serialize(Graph) ->
serialize_with(
fun(Label) -> maps:from_list([{<<"label"/utf8>>, Label}]) end,
fun(Label@1) -> maps:from_list([{<<"label"/utf8>>, Label@1}]) end,
default_options(),
Graph
).
-file("src/yog_io/gdf.gleam", 362).
?DOC(
" Serializes a graph to GDF format with integer edge weights.\n"
"\n"
" This is a convenience function for the common case of graphs with\n"
" integer weights. Node data is used as labels, and edge weights are\n"
" serialized to the \"weight\" column.\n"
"\n"
" **Time Complexity:** O(V + E)\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" import yog/model.{Directed}\n"
" import yog_io/gdf\n"
"\n"
" let graph =\n"
" model.new(Directed)\n"
" |> model.add_node(1, \"Alice\")\n"
" |> model.add_node(2, \"Bob\")\n"
"\n"
" let assert Ok(graph) = model.add_edge(graph, from: 1, to: 2, with: 5)\n"
"\n"
" let gdf = gdf.serialize_weighted(graph)\n"
" ```\n"
).
-spec serialize_weighted(yog@model:graph(binary(), integer())) -> binary().
serialize_weighted(Graph) ->
serialize_with(
fun(Label) -> maps:from_list([{<<"label"/utf8>>, Label}]) end,
fun(Weight) ->
maps:from_list(
[{<<"weight"/utf8>>, erlang:integer_to_binary(Weight)}]
)
end,
default_options(),
Graph
).
-file("src/yog_io/gdf.gleam", 388).
?DOC(
" Writes a graph to a GDF file.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" import yog/model.{Directed}\n"
" import yog_io/gdf\n"
"\n"
" let graph =\n"
" model.new(Directed)\n"
" |> model.add_node(1, \"Start\")\n"
" |> model.add_node(2, \"End\")\n"
"\n"
" let assert Ok(graph) = model.add_edge(graph, from: 1, to: 2, with: \"connection\")\n"
"\n"
" let assert Ok(Nil) = gdf.write(\"mygraph.gdf\", graph)\n"
" ```\n"
).
-spec write(binary(), yog@model:graph(binary(), binary())) -> {ok, nil} |
{error, simplifile:file_error()}.
write(Path, Graph) ->
Content = serialize(Graph),
simplifile:write(Path, Content).
-file("src/yog_io/gdf.gleam", 397).
?DOC(" Writes a graph to a GDF file with custom attribute mappers.\n").
-spec write_with(
binary(),
fun((ABPI) -> gleam@dict:dict(binary(), binary())),
fun((ABPJ) -> gleam@dict:dict(binary(), binary())),
gdf_options(),
yog@model:graph(ABPI, ABPJ)
) -> {ok, nil} | {error, simplifile:file_error()}.
write_with(Path, Node_attr, Edge_attr, Options, Graph) ->
Content = serialize_with(Node_attr, Edge_attr, Options, Graph),
simplifile:write(Path, Content).
-file("src/yog_io/gdf.gleam", 808).
-spec add_edge_unchecked(
yog@model:graph(ABQW, ABQX),
integer(),
integer(),
ABQX
) -> yog@model:graph(ABQW, ABQX).
add_edge_unchecked(Graph, Src, Dst, Weight) ->
Out_update = case gleam_stdlib:map_get(erlang:element(4, Graph), Src) of
{ok, Edges} ->
gleam@dict:insert(Edges, Dst, Weight);
{error, _} ->
maps:from_list([{Dst, Weight}])
end,
In_update = case gleam_stdlib:map_get(erlang:element(5, Graph), Dst) of
{ok, Edges@1} ->
gleam@dict:insert(Edges@1, Src, Weight);
{error, _} ->
maps:from_list([{Src, Weight}])
end,
New_out = gleam@dict:insert(erlang:element(4, Graph), Src, Out_update),
New_in = gleam@dict:insert(erlang:element(5, Graph), Dst, In_update),
case erlang:element(2, Graph) of
directed ->
{graph,
erlang:element(2, Graph),
erlang:element(3, Graph),
New_out,
New_in};
undirected ->
Out_update_rev = case gleam_stdlib:map_get(
erlang:element(4, Graph),
Dst
) of
{ok, Edges@2} ->
gleam@dict:insert(Edges@2, Src, Weight);
{error, _} ->
maps:from_list([{Src, Weight}])
end,
In_update_rev = case gleam_stdlib:map_get(
erlang:element(5, Graph),
Src
) of
{ok, Edges@3} ->
gleam@dict:insert(Edges@3, Dst, Weight);
{error, _} ->
maps:from_list([{Dst, Weight}])
end,
{graph,
erlang:element(2, Graph),
erlang:element(3, Graph),
gleam@dict:insert(New_out, Dst, Out_update_rev),
gleam@dict:insert(New_in, Src, In_update_rev)}
end.
-file("src/yog_io/gdf.gleam", 441).
?DOC(
" Deserializes a GDF string into a graph with custom data mappers.\n"
"\n"
" This function allows you to control how GDF columns are converted\n"
" to your node and edge data types. Use `deserialize` for simple cases\n"
" where you want node/edge data as string dictionaries.\n"
"\n"
" **Time Complexity:** O(V + E)\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" import gleam/dict\n"
" import yog_io/gdf\n"
"\n"
" type Person {\n"
" Person(name: String, age: Int)\n"
" }\n"
"\n"
" let node_folder = fn(attrs: dict.Dict(String, String)) {\n"
" let name = dict.get(attrs, \"label\") |> result.unwrap(\"\")\n"
" let age = dict.get(attrs, \"age\") |> result.unwrap(\"0\") |> int.parse |> result.unwrap(0)\n"
" Person(name, age)\n"
" }\n"
"\n"
" let gdf = \"...\"\n"
" let assert Ok(graph) = gdf.deserialize_with(node_folder, fn(attrs) {\n"
" dict.get(attrs, \"weight\") |> result.unwrap(\"\")\n"
" }, gdf)\n"
" ```\n"
).
-spec deserialize_with(
fun((gleam@dict:dict(binary(), binary())) -> ABPO),
fun((gleam@dict:dict(binary(), binary())) -> ABPP),
binary()
) -> {ok, yog@model:graph(ABPO, ABPP)} | {error, binary()}.
deserialize_with(Node_folder, Edge_folder, Gdf) ->
Lines = gleam@string:split(Gdf, <<"\n"/utf8>>),
Node_def_idx = find_line_index(Lines, <<"nodedef>"/utf8>>),
Edge_def_idx = find_line_index(Lines, <<"edgedef>"/utf8>>),
case Node_def_idx of
{error, _} ->
{error, <<"Missing nodedef> section"/utf8>>};
{ok, Node_idx} ->
Edge_idx = case Edge_def_idx of
{ok, Idx} ->
Idx;
{error, _} ->
erlang:length(Lines)
end,
Node_header_line = case begin
_pipe = gleam@list:drop(Lines, Node_idx),
gleam@list:first(_pipe)
end of
{ok, Line} ->
Line;
{error, _} ->
<<"nodedef>name"/utf8>>
end,
Node_columns = parse_header(Node_header_line),
Node_data_lines = begin
_pipe@1 = Lines,
_pipe@2 = gleam@list:drop(_pipe@1, Node_idx + 1),
_pipe@3 = gleam@list:take(_pipe@2, (Edge_idx - Node_idx) - 1),
gleam@list:filter(
_pipe@3,
fun(Line@1) ->
not gleam@string:is_empty(gleam@string:trim(Line@1))
end
)
end,
{Edge_columns, Edge_data_lines, Is_directed} = case Edge_def_idx of
{ok, E_idx} ->
Edge_header_line = case begin
_pipe@4 = gleam@list:drop(Lines, E_idx),
gleam@list:first(_pipe@4)
end of
{ok, Line@2} ->
Line@2;
{error, _} ->
<<"edgedef>node1,node2"/utf8>>
end,
E_columns = parse_header(Edge_header_line),
E_data_lines = begin
_pipe@5 = Lines,
_pipe@6 = gleam@list:drop(_pipe@5, E_idx + 1),
gleam@list:filter(
_pipe@6,
fun(Line@3) ->
not gleam@string:is_empty(
gleam@string:trim(Line@3)
)
end
)
end,
Has_directed = gleam@list:contains(
E_columns,
<<"directed"/utf8>>
),
{E_columns, E_data_lines, Has_directed};
{error, _} ->
{[], [], false}
end,
Graph_type = case Edge_def_idx of
{ok, E_idx@1} ->
Edge_header = case begin
_pipe@7 = gleam@list:drop(Lines, E_idx@1),
gleam@list:first(_pipe@7)
end of
{ok, Line@4} ->
Line@4;
{error, _} ->
<<""/utf8>>
end,
case gleam_stdlib:contains_string(
Edge_header,
<<"directed"/utf8>>
) of
true ->
case gleam@list:first(Edge_data_lines) of
{ok, First_edge} ->
Parts = parse_csv_line(First_edge),
Dir_idx = find_column_index(
Edge_columns,
<<"directed"/utf8>>
),
Parts_length = erlang:length(Parts),
case Dir_idx of
{ok, Idx@1} ->
case Idx@1 < Parts_length of
true ->
case begin
_pipe@8 = gleam@list:drop(
Parts,
Idx@1
),
gleam@list:first(
_pipe@8
)
end of
{ok, <<"true"/utf8>>} ->
directed;
{ok, <<"True"/utf8>>} ->
directed;
{ok, <<"TRUE"/utf8>>} ->
directed;
_ ->
undirected
end;
false ->
undirected
end;
_ ->
undirected
end;
{error, _} ->
undirected
end;
false ->
undirected
end;
{error, _} ->
undirected
end,
Graph = yog@model:new(Graph_type),
Graph@1 = gleam@list:fold(
Node_data_lines,
Graph,
fun(G, Line@5) ->
Values = parse_csv_line(Line@5),
case gleam@list:first(Values) of
{ok, Id_str} ->
case gleam_stdlib:parse_int(
gleam@string:trim(Id_str)
) of
{ok, Id} ->
Attrs = begin
_pipe@9 = gleam@list:zip(
Node_columns,
Values
),
maps:from_list(_pipe@9)
end,
Node_data = Node_folder(Attrs),
yog@model:add_node(G, Id, Node_data);
{error, _} ->
G
end;
{error, _} ->
G
end
end
),
Graph@2 = gleam@list:fold(
Edge_data_lines,
Graph@1,
fun(G@1, Line@6) ->
Values@1 = parse_csv_line(Line@6),
case {gleam@list:first(Values@1),
begin
_pipe@10 = gleam@list:drop(Values@1, 1),
gleam@list:first(_pipe@10)
end} of
{{ok, Src_str}, {ok, Tgt_str}} ->
case {gleam_stdlib:parse_int(
gleam@string:trim(Src_str)
),
gleam_stdlib:parse_int(
gleam@string:trim(Tgt_str)
)} of
{{ok, Src}, {ok, Tgt}} ->
Skip_count = case Is_directed of
true ->
3;
false ->
2
end,
Remaining_values = gleam@list:drop(
Values@1,
Skip_count
),
Remaining_columns = begin
_pipe@11 = gleam@list:drop(
Edge_columns,
Skip_count
),
gleam@list:filter(
_pipe@11,
fun(Col) ->
Col /= <<"directed"/utf8>>
end
)
end,
Attrs@1 = begin
_pipe@12 = gleam@list:zip(
Remaining_columns,
Remaining_values
),
maps:from_list(_pipe@12)
end,
Edge_data = Edge_folder(Attrs@1),
G_with_src = case gleam@dict:has_key(
erlang:element(3, G@1),
Src
) of
true ->
G@1;
false ->
yog@model:add_node(
G@1,
Src,
Node_folder(maps:new())
)
end,
G_with_both = case gleam@dict:has_key(
erlang:element(3, G_with_src),
Tgt
) of
true ->
G_with_src;
false ->
yog@model:add_node(
G_with_src,
Tgt,
Node_folder(maps:new())
)
end,
add_edge_unchecked(
G_with_both,
Src,
Tgt,
Edge_data
);
{_, _} ->
G@1
end;
{_, _} ->
G@1
end
end
),
{ok, Graph@2}
end.
-file("src/yog_io/gdf.gleam", 640).
?DOC(
" Deserializes a GDF string to a graph.\n"
"\n"
" This is a simplified version of `deserialize_with` for graphs where\n"
" you want node data and edge data as string dictionaries containing all attributes.\n"
"\n"
" **Time Complexity:** O(V + E)\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" import yog_io/gdf\n"
"\n"
" let gdf_string = \"...\"\n"
" let assert Ok(graph) = gdf.deserialize(gdf_string)\n"
"\n"
" // Access node data\n"
" let node1_data = dict.get(graph.nodes, 1) // Dict(String, String)\n"
" let label = dict.get(node1_data, \"label\")\n"
" ```\n"
).
-spec deserialize(binary()) -> {ok,
yog@model:graph(gleam@dict:dict(binary(), binary()), gleam@dict:dict(binary(), binary()))} |
{error, binary()}.
deserialize(Gdf) ->
deserialize_with(fun(Attrs) -> Attrs end, fun(Attrs@1) -> Attrs@1 end, Gdf).
-file("src/yog_io/gdf.gleam", 653).
?DOC(
" Reads a graph from a GDF file.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" import yog_io/gdf\n"
"\n"
" let assert Ok(graph) = gdf.read(\"graph.gdf\")\n"
" ```\n"
).
-spec read(binary()) -> {ok,
yog@model:graph(gleam@dict:dict(binary(), binary()), gleam@dict:dict(binary(), binary()))} |
{error, binary()}.
read(Path) ->
case simplifile:read(Path) of
{ok, Content} ->
deserialize(Content);
{error, E} ->
{error,
<<"Failed to read file: "/utf8,
(simplifile:describe_error(E))/binary>>}
end.
-file("src/yog_io/gdf.gleam", 661).
?DOC(" Reads a graph from a GDF file with custom data mappers.\n").
-spec read_with(
binary(),
fun((gleam@dict:dict(binary(), binary())) -> ABPY),
fun((gleam@dict:dict(binary(), binary())) -> ABPZ)
) -> {ok, yog@model:graph(ABPY, ABPZ)} | {error, binary()}.
read_with(Path, Node_folder, Edge_folder) ->
case simplifile:read(Path) of
{ok, Content} ->
deserialize_with(Node_folder, Edge_folder, Content);
{error, E} ->
{error,
<<"Failed to read file: "/utf8,
(simplifile:describe_error(E))/binary>>}
end.