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@graphml.erl
Raw

src/yog_io@graphml.erl

-module(yog_io@graphml).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/yog_io/graphml.gleam").
-export([default_options/0, deserialize_with/3, deserialize/1, read/1, read_with/3, serialize_with_types_and_options/4, serialize_with_types/3, serialize_with_options/4, serialize_with/3, serialize/1, write/2, write_with/4, write_with_types/4]).
-export_type([attribute_type/0, graph_m_l_options/0, parse_state/2]).
-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(
" GraphML (Graph Markup Language) serialization support.\n"
"\n"
" Provides functions to serialize and deserialize graphs in the GraphML format,\n"
" an XML-based format widely supported by graph visualization and analysis tools\n"
" like Gephi, yEd, Cytoscape, and NetworkX.\n"
"\n"
" ## Quick Start\n"
"\n"
" ```gleam\n"
" import yog/model.{Directed}\n"
" import yog_io/graphml\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: \"friend\")\n"
"\n"
" // Serialize to GraphML\n"
" let xml = graphml.serialize(graph)\n"
"\n"
" // Write to file\n"
" let assert Ok(Nil) = graphml.write(\"graph.graphml\", graph)\n"
"\n"
" // Read from file\n"
" let assert Ok(loaded) = graphml.read(\"graph.graphml\")\n"
" ```\n"
"\n"
" ## Format Overview\n"
"\n"
" GraphML is an XML-based format that supports:\n"
" - **Nodes** with custom attributes\n"
" - **Edges** with custom attributes\n"
" - **Directed and undirected** graphs\n"
" - **Hierarchical graphs** (not yet supported)\n"
"\n"
" ## References\n"
"\n"
" - [GraphML Specification](http://graphml.graphdrawing.org/specification.html)\n"
" - [GraphML Primer](http://graphml.graphdrawing.org/primer/graphml-primer.html)\n"
).
-type attribute_type() :: string_type |
int_type |
float_type |
double_type |
boolean_type |
long_type.
-type graph_m_l_options() :: {graph_m_l_options, integer(), boolean()}.
-type parse_state(AEWG, AEWH) :: {parse_state,
yog@model:graph(AEWG, AEWH),
gleam@option:option(binary()),
gleam@option:option(integer()),
gleam@dict:dict(binary(), binary()),
gleam@option:option(integer()),
gleam@option:option(integer()),
gleam@dict:dict(binary(), binary()),
gleam@option:option(binary()),
fun((gleam@dict:dict(binary(), binary())) -> AEWG),
fun((gleam@dict:dict(binary(), binary())) -> AEWH)}.
-file("src/yog_io/graphml.gleam", 111).
?DOC(" Default GraphML serialization options.\n").
-spec default_options() -> graph_m_l_options().
default_options() ->
{graph_m_l_options, 2, true}.
-file("src/yog_io/graphml.gleam", 116).
?DOC(" Convert AttributeType to GraphML attr.type string.\n").
-spec attribute_type_to_string(attribute_type()) -> binary().
attribute_type_to_string(Attr_type) ->
case Attr_type of
string_type ->
<<"string"/utf8>>;
int_type ->
<<"int"/utf8>>;
float_type ->
<<"float"/utf8>>;
double_type ->
<<"double"/utf8>>;
boolean_type ->
<<"boolean"/utf8>>;
long_type ->
<<"long"/utf8>>
end.
-file("src/yog_io/graphml.gleam", 755).
-spec find_graph_type(list(xmlm:signal())) -> gleam@option:option(yog@model:graph_type()).
find_graph_type(Signals) ->
case Signals of
[] ->
none;
[First | Rest] ->
case First of
{element_start, Tag} ->
case erlang:element(3, erlang:element(2, Tag)) of
<<"graph"/utf8>> ->
case gleam@list:find(
erlang:element(3, Tag),
fun(Attr) ->
erlang:element(3, erlang:element(2, Attr))
=:= <<"edgedefault"/utf8>>
end
) of
{ok, Attr@1} ->
case erlang:element(3, Attr@1) of
<<"directed"/utf8>> ->
{some, directed};
_ ->
{some, undirected}
end;
{error, _} ->
{some, undirected}
end;
_ ->
find_graph_type(Rest)
end;
_ ->
find_graph_type(Rest)
end
end.
-file("src/yog_io/graphml.gleam", 822).
-spec process_element_start(parse_state(AEZP, AEZQ), xmlm:tag()) -> parse_state(AEZP, AEZQ).
process_element_start(State, Tag) ->
case erlang:element(3, erlang:element(2, Tag)) of
<<"node"/utf8>> ->
case begin
_pipe = gleam@list:find(
erlang:element(3, Tag),
fun(Attr) ->
erlang:element(3, erlang:element(2, Attr)) =:= <<"id"/utf8>>
end
),
gleam@result:'try'(
_pipe,
fun(Attr@1) ->
gleam_stdlib:parse_int(erlang:element(3, Attr@1))
end
)
end of
{ok, Id} ->
{parse_state,
erlang:element(2, State),
{some, <<"node"/utf8>>},
{some, Id},
maps:new(),
erlang:element(6, State),
erlang:element(7, State),
erlang:element(8, State),
erlang:element(9, State),
erlang:element(10, State),
erlang:element(11, State)};
{error, _} ->
State
end;
<<"edge"/utf8>> ->
Src = begin
_pipe@1 = gleam@list:find(
erlang:element(3, Tag),
fun(Attr@2) ->
erlang:element(3, erlang:element(2, Attr@2)) =:= <<"source"/utf8>>
end
),
_pipe@2 = gleam@result:'try'(
_pipe@1,
fun(Attr@3) ->
gleam_stdlib:parse_int(erlang:element(3, Attr@3))
end
),
gleam@option:from_result(_pipe@2)
end,
Tgt = begin
_pipe@3 = gleam@list:find(
erlang:element(3, Tag),
fun(Attr@4) ->
erlang:element(3, erlang:element(2, Attr@4)) =:= <<"target"/utf8>>
end
),
_pipe@4 = gleam@result:'try'(
_pipe@3,
fun(Attr@5) ->
gleam_stdlib:parse_int(erlang:element(3, Attr@5))
end
),
gleam@option:from_result(_pipe@4)
end,
{parse_state,
erlang:element(2, State),
{some, <<"edge"/utf8>>},
erlang:element(4, State),
erlang:element(5, State),
Src,
Tgt,
maps:new(),
erlang:element(9, State),
erlang:element(10, State),
erlang:element(11, State)};
<<"data"/utf8>> ->
case gleam@list:find(
erlang:element(3, Tag),
fun(Attr@6) ->
erlang:element(3, erlang:element(2, Attr@6)) =:= <<"key"/utf8>>
end
) of
{ok, Attr@7} ->
{parse_state,
erlang:element(2, State),
erlang:element(3, State),
erlang:element(4, State),
erlang:element(5, State),
erlang:element(6, State),
erlang:element(7, State),
erlang:element(8, State),
{some, erlang:element(3, Attr@7)},
erlang:element(10, State),
erlang:element(11, State)};
{error, _} ->
State
end;
_ ->
State
end.
-file("src/yog_io/graphml.gleam", 941).
-spec process_data(parse_state(AFAB, AFAC), binary()) -> parse_state(AFAB, AFAC).
process_data(State, Text) ->
case erlang:element(9, State) of
{some, Key} ->
case erlang:element(3, State) of
{some, <<"node"/utf8>>} ->
{parse_state,
erlang:element(2, State),
erlang:element(3, State),
erlang:element(4, State),
gleam@dict:insert(erlang:element(5, State), Key, Text),
erlang:element(6, State),
erlang:element(7, State),
erlang:element(8, State),
erlang:element(9, State),
erlang:element(10, State),
erlang:element(11, State)};
{some, <<"edge"/utf8>>} ->
{parse_state,
erlang:element(2, State),
erlang:element(3, State),
erlang:element(4, State),
erlang:element(5, State),
erlang:element(6, State),
erlang:element(7, State),
gleam@dict:insert(erlang:element(8, State), Key, Text),
erlang:element(9, State),
erlang:element(10, State),
erlang:element(11, State)};
_ ->
State
end;
none ->
State
end.
-file("src/yog_io/graphml.gleam", 962).
-spec add_edge_unchecked(
yog@model:graph(AFAH, AFAI),
integer(),
integer(),
AFAI
) -> yog@model:graph(AFAH, AFAI).
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/graphml.gleam", 874).
-spec process_element_end(parse_state(AEZV, AEZW)) -> parse_state(AEZV, AEZW).
process_element_end(State) ->
case erlang:element(9, State) of
{some, _} ->
{parse_state,
erlang:element(2, State),
erlang:element(3, State),
erlang:element(4, State),
erlang:element(5, State),
erlang:element(6, State),
erlang:element(7, State),
erlang:element(8, State),
none,
erlang:element(10, State),
erlang:element(11, State)};
none ->
case erlang:element(3, State) of
{some, <<"node"/utf8>>} ->
case erlang:element(4, State) of
{some, Id} ->
Node_data = (erlang:element(10, State))(
erlang:element(5, State)
),
New_graph = yog@model:add_node(
erlang:element(2, State),
Id,
Node_data
),
{parse_state,
New_graph,
none,
none,
maps:new(),
erlang:element(6, State),
erlang:element(7, State),
erlang:element(8, State),
erlang:element(9, State),
erlang:element(10, State),
erlang:element(11, State)};
none ->
State
end;
{some, <<"edge"/utf8>>} ->
case {erlang:element(6, State), erlang:element(7, State)} of
{{some, Src}, {some, Tgt}} ->
Edge_data = (erlang:element(11, State))(
erlang:element(8, State)
),
G_with_src = case gleam@dict:has_key(
erlang:element(3, erlang:element(2, State)),
Src
) of
true ->
erlang:element(2, State);
false ->
yog@model:add_node(
erlang:element(2, State),
Src,
(erlang:element(10, State))(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,
(erlang:element(10, State))(maps:new())
)
end,
New_graph@1 = add_edge_unchecked(
G_with_both,
Src,
Tgt,
Edge_data
),
{parse_state,
New_graph@1,
none,
erlang:element(4, State),
erlang:element(5, State),
none,
none,
maps:new(),
erlang:element(9, State),
erlang:element(10, State),
erlang:element(11, State)};
{_, _} ->
State
end;
_ ->
State
end
end.
-file("src/yog_io/graphml.gleam", 810).
-spec process_signal(parse_state(AEZJ, AEZK), xmlm:signal()) -> parse_state(AEZJ, AEZK).
process_signal(State, Signal) ->
case Signal of
{element_start, Tag} ->
process_element_start(State, Tag);
element_end ->
process_element_end(State);
{data, Text} ->
process_data(State, Text);
{dtd, _} ->
State
end.
-file("src/yog_io/graphml.gleam", 786).
-spec parse_graphml_signals(
list(xmlm:signal()),
yog@model:graph(AEZB, AEZC),
fun((gleam@dict:dict(binary(), binary())) -> AEZB),
fun((gleam@dict:dict(binary(), binary())) -> AEZC)
) -> {ok, yog@model:graph(AEZB, AEZC)} | {error, binary()}.
parse_graphml_signals(Signals, Graph, Node_folder, Edge_folder) ->
State = {parse_state,
Graph,
none,
none,
maps:new(),
none,
none,
maps:new(),
none,
Node_folder,
Edge_folder},
Final_state = gleam@list:fold(Signals, State, fun process_signal/2),
{ok, erlang:element(2, Final_state)}.
-file("src/yog_io/graphml.gleam", 630).
?DOC(
" Deserializes a GraphML string into a graph with custom data mappers.\n"
"\n"
" This function allows you to control how GraphML attributes 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/model.{Directed}\n"
" import yog_io/graphml\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, \"name\") |> 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 edge_folder = fn(attrs: dict.Dict(String, String)) {\n"
" dict.get(attrs, \"type\") |> result.unwrap(\"\")\n"
" }\n"
"\n"
" let xml = \"...\"\n"
" let assert Ok(graph) = graphml.deserialize_with(node_folder, edge_folder, xml)\n"
" ```\n"
).
-spec deserialize_with(
fun((gleam@dict:dict(binary(), binary())) -> AEYI),
fun((gleam@dict:dict(binary(), binary())) -> AEYJ),
binary()
) -> {ok, yog@model:graph(AEYI, AEYJ)} | {error, binary()}.
deserialize_with(Node_folder, Edge_folder, Xml) ->
Input = xmlm:from_string(Xml),
case xmlm:signals(Input) of
{error, _} ->
{error, <<"Failed to parse XML"/utf8>>};
{ok, {Signals, _}} ->
Graph_type = case find_graph_type(Signals) of
{some, Gt} ->
Gt;
none ->
undirected
end,
Graph = yog@model:new(Graph_type),
parse_graphml_signals(Signals, Graph, Node_folder, Edge_folder)
end.
-file("src/yog_io/graphml.gleam", 674).
?DOC(
" Deserializes a GraphML 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/graphml\n"
"\n"
" let xml = \"...\"\n"
" let assert Ok(graph) = graphml.deserialize(xml)\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(Xml) ->
deserialize_with(fun(Attrs) -> Attrs end, fun(Attrs@1) -> Attrs@1 end, Xml).
-file("src/yog_io/graphml.gleam", 694).
?DOC(
" Reads a graph from a GraphML file.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" import yog_io/graphml\n"
"\n"
" let assert Ok(graph) = graphml.read(\"graph.graphml\")\n"
"\n"
" // Access node data\n"
" import gleam/dict\n"
" for node in dict.to_list(graph.nodes) {\n"
" let #(id, data) = node\n"
" io.debug(\"Node \" <> int.to_string(id) <> \": \" <> data)\n"
" }\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/graphml.gleam", 724).
?DOC(
" Reads a graph from a GraphML file with custom data mappers.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" import gleam/dict\n"
" import yog_io/graphml\n"
"\n"
" type Person {\n"
" Person(name: String, age: Int)\n"
" }\n"
"\n"
" let node_folder = fn(attrs) {\n"
" Person(\n"
" dict.get(attrs, \"name\") |> result.unwrap(\"\"),\n"
" dict.get(attrs, \"age\") |> result.unwrap(\"0\") |> int.parse |> result.unwrap(0)\n"
" )\n"
" }\n"
"\n"
" let assert Ok(graph) = graphml.read_with(\"people.graphml\", node_folder, fn(attrs) {\n"
" dict.get(attrs, \"weight\") |> result.unwrap(\"0\") |> int.parse |> result.unwrap(0)\n"
" })\n"
" ```\n"
).
-spec read_with(
binary(),
fun((gleam@dict:dict(binary(), binary())) -> AEYS),
fun((gleam@dict:dict(binary(), binary())) -> AEYT)
) -> {ok, yog@model:graph(AEYS, AEYT)} | {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.
-file("src/yog_io/graphml.gleam", 1004).
-spec escape_xml(binary()) -> binary().
escape_xml(Text) ->
_pipe = Text,
_pipe@1 = gleam@string:replace(_pipe, <<"&"/utf8>>, <<"&amp;"/utf8>>),
_pipe@2 = gleam@string:replace(_pipe@1, <<"<"/utf8>>, <<"&lt;"/utf8>>),
_pipe@3 = gleam@string:replace(_pipe@2, <<">"/utf8>>, <<"&gt;"/utf8>>),
_pipe@4 = gleam@string:replace(_pipe@3, <<"\""/utf8>>, <<"&quot;"/utf8>>),
gleam@string:replace(_pipe@4, <<"'"/utf8>>, <<"&apos;"/utf8>>).
-file("src/yog_io/graphml.gleam", 272).
-spec serialize_internal(
fun((AEXI) -> gleam@dict:dict(binary(), {binary(), attribute_type()})),
fun((AEXL) -> gleam@dict:dict(binary(), {binary(), attribute_type()})),
graph_m_l_options(),
yog@model:graph(AEXI, AEXL),
boolean()
) -> binary().
serialize_internal(Node_attr, Edge_attr, Options, Graph, _) ->
Nodes_list = maps:to_list(erlang:element(3, Graph)),
Node_key_types = begin
_pipe = Nodes_list,
gleam@list:fold(
_pipe,
maps:new(),
fun(Acc, Entry) ->
{_, Data} = Entry,
Attrs = Node_attr(Data),
gleam@dict:fold(
Attrs,
Acc,
fun(Acc2, Key, Value_and_type) ->
{_, Attr_type} = Value_and_type,
case gleam@dict:has_key(Acc2, Key) of
true ->
Acc2;
false ->
gleam@dict:insert(Acc2, Key, Attr_type)
end
end
)
end
)
end,
Edges_list = begin
_pipe@1 = maps:to_list(erlang:element(4, Graph)),
gleam@list:flat_map(
_pipe@1,
fun(Entry@1) ->
{Src, Targets} = Entry@1,
_pipe@2 = maps:to_list(Targets),
gleam@list:map(
_pipe@2,
fun(Target_entry) ->
{Dst, Weight} = Target_entry,
{Src, Dst, Weight}
end
)
end
)
end,
Edge_key_types = begin
_pipe@3 = Edges_list,
gleam@list:fold(
_pipe@3,
maps:new(),
fun(Acc@1, Entry@2) ->
{_, _, Data@1} = Entry@2,
Attrs@1 = Edge_attr(Data@1),
gleam@dict:fold(
Attrs@1,
Acc@1,
fun(Acc2@1, Key@1, Value_and_type@1) ->
{_, Attr_type@1} = Value_and_type@1,
case gleam@dict:has_key(Acc2@1, Key@1) of
true ->
Acc2@1;
false ->
gleam@dict:insert(Acc2@1, Key@1, Attr_type@1)
end
end
)
end
)
end,
Builder = gleam@string_tree:new(),
Builder@1 = case erlang:element(3, Options) of
true ->
gleam@string_tree:append(
Builder,
<<"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"/utf8>>
);
false ->
Builder
end,
Builder@2 = begin
_pipe@4 = gleam@string_tree:append(
Builder@1,
<<"<graphml xmlns=\"http://graphml.graphdrawing.org/xmlns\""/utf8>>
),
_pipe@5 = gleam@string_tree:append(
_pipe@4,
<<" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""/utf8>>
),
_pipe@6 = gleam@string_tree:append(
_pipe@5,
<<" xsi:schemaLocation=\"http://graphml.graphdrawing.org/xmlns"/utf8>>
),
gleam@string_tree:append(
_pipe@6,
<<" http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd\">\n"/utf8>>
)
end,
Builder@3 = gleam@dict:fold(
Node_key_types,
Builder@2,
fun(B, Key@2, Attr_type@2) ->
_pipe@7 = gleam@string_tree:append(B, <<" <key id=\""/utf8>>),
_pipe@8 = gleam@string_tree:append(_pipe@7, escape_xml(Key@2)),
_pipe@9 = gleam@string_tree:append(
_pipe@8,
<<"\" for=\"node\" attr.name=\""/utf8>>
),
_pipe@10 = gleam@string_tree:append(_pipe@9, escape_xml(Key@2)),
_pipe@11 = gleam@string_tree:append(
_pipe@10,
<<"\" attr.type=\""/utf8>>
),
_pipe@12 = gleam@string_tree:append(
_pipe@11,
attribute_type_to_string(Attr_type@2)
),
gleam@string_tree:append(_pipe@12, <<"\"/>\n"/utf8>>)
end
),
Builder@4 = gleam@dict:fold(
Edge_key_types,
Builder@3,
fun(B@1, Key@3, Attr_type@3) ->
_pipe@13 = gleam@string_tree:append(B@1, <<" <key id=\""/utf8>>),
_pipe@14 = gleam@string_tree:append(_pipe@13, escape_xml(Key@3)),
_pipe@15 = gleam@string_tree:append(
_pipe@14,
<<"\" for=\"edge\" attr.name=\""/utf8>>
),
_pipe@16 = gleam@string_tree:append(_pipe@15, escape_xml(Key@3)),
_pipe@17 = gleam@string_tree:append(
_pipe@16,
<<"\" attr.type=\""/utf8>>
),
_pipe@18 = gleam@string_tree:append(
_pipe@17,
attribute_type_to_string(Attr_type@3)
),
gleam@string_tree:append(_pipe@18, <<"\"/>\n"/utf8>>)
end
),
Edge_default = case erlang:element(2, Graph) of
directed ->
<<"directed"/utf8>>;
undirected ->
<<"undirected"/utf8>>
end,
Builder@5 = begin
_pipe@19 = gleam@string_tree:append(
Builder@4,
<<" <graph id=\"G\" edgedefault=\""/utf8>>
),
_pipe@20 = gleam@string_tree:append(_pipe@19, Edge_default),
gleam@string_tree:append(_pipe@20, <<"\">\n"/utf8>>)
end,
Builder@6 = gleam@list:fold(
Nodes_list,
Builder@5,
fun(B@2, Entry@3) ->
{Id, Data@2} = Entry@3,
Attrs@2 = Node_attr(Data@2),
B@3 = begin
_pipe@21 = gleam@string_tree:append(
B@2,
<<" <node id=\""/utf8>>
),
_pipe@22 = gleam@string_tree:append(
_pipe@21,
erlang:integer_to_binary(Id)
),
gleam@string_tree:append(_pipe@22, <<"\">\n"/utf8>>)
end,
B@4 = gleam@dict:fold(
Attrs@2,
B@3,
fun(B2, Key@4, Value_and_type@2) ->
{Value, _} = Value_and_type@2,
_pipe@23 = gleam@string_tree:append(
B2,
<<" <data key=\""/utf8>>
),
_pipe@24 = gleam@string_tree:append(
_pipe@23,
escape_xml(Key@4)
),
_pipe@25 = gleam@string_tree:append(
_pipe@24,
<<"\">"/utf8>>
),
_pipe@26 = gleam@string_tree:append(
_pipe@25,
escape_xml(Value)
),
gleam@string_tree:append(_pipe@26, <<"</data>\n"/utf8>>)
end
),
gleam@string_tree:append(B@4, <<" </node>\n"/utf8>>)
end
),
Edges_to_output = case erlang:element(2, Graph) of
directed ->
Edges_list;
undirected ->
gleam@list:filter(
Edges_list,
fun(Entry@4) ->
{Src@1, Dst@1, _} = Entry@4,
Src@1 =< Dst@1
end
)
end,
Builder@7 = gleam@list:fold(
Edges_to_output,
Builder@6,
fun(B@5, Entry@5) ->
{Src@2, Dst@2, Data@3} = Entry@5,
Attrs@3 = Edge_attr(Data@3),
B@6 = begin
_pipe@27 = gleam@string_tree:append(
B@5,
<<" <edge source=\""/utf8>>
),
_pipe@28 = gleam@string_tree:append(
_pipe@27,
erlang:integer_to_binary(Src@2)
),
_pipe@29 = gleam@string_tree:append(
_pipe@28,
<<"\" target=\""/utf8>>
),
_pipe@30 = gleam@string_tree:append(
_pipe@29,
erlang:integer_to_binary(Dst@2)
),
gleam@string_tree:append(_pipe@30, <<"\">\n"/utf8>>)
end,
B@7 = gleam@dict:fold(
Attrs@3,
B@6,
fun(B2@1, Key@5, Value_and_type@3) ->
{Value@1, _} = Value_and_type@3,
_pipe@31 = gleam@string_tree:append(
B2@1,
<<" <data key=\""/utf8>>
),
_pipe@32 = gleam@string_tree:append(
_pipe@31,
escape_xml(Key@5)
),
_pipe@33 = gleam@string_tree:append(
_pipe@32,
<<"\">"/utf8>>
),
_pipe@34 = gleam@string_tree:append(
_pipe@33,
escape_xml(Value@1)
),
gleam@string_tree:append(_pipe@34, <<"</data>\n"/utf8>>)
end
),
gleam@string_tree:append(B@7, <<" </edge>\n"/utf8>>)
end
),
Builder@8 = gleam@string_tree:append(Builder@7, <<" </graph>\n"/utf8>>),
Builder@9 = gleam@string_tree:append(Builder@8, <<"</graphml>\n"/utf8>>),
unicode:characters_to_binary(Builder@9).
-file("src/yog_io/graphml.gleam", 227).
?DOC(" Serializes a graph to GraphML with typed attributes and custom options.\n").
-spec serialize_with_types_and_options(
fun((AEXA) -> gleam@dict:dict(binary(), {binary(), attribute_type()})),
fun((AEXB) -> gleam@dict:dict(binary(), {binary(), attribute_type()})),
graph_m_l_options(),
yog@model:graph(AEXA, AEXB)
) -> binary().
serialize_with_types_and_options(Node_attr, Edge_attr, Options, Graph) ->
serialize_internal(fun(N) -> _pipe = Node_attr(N),
gleam@dict:map_values(_pipe, fun(_, V) -> V end) end, fun(E) ->
_pipe@1 = Edge_attr(E),
gleam@dict:map_values(_pipe@1, fun(_, V@1) -> V@1 end)
end, Options, Graph, true).
-file("src/yog_io/graphml.gleam", 213).
?DOC(
" Serializes a graph to GraphML with typed attributes for Gephi compatibility.\n"
"\n"
" This function allows you to specify the GraphML data type for each attribute,\n"
" which is essential for proper Gephi compatibility. Numeric attributes (Int, Float,\n"
" Double) will be recognized correctly for visualizations, layouts, and filters.\n"
"\n"
" **Time Complexity:** O(V + E)\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" import gleam/dict\n"
" import gleam/int\n"
" import yog/model.{Directed}\n"
" import yog_io/graphml.{DoubleType, IntType, StringType}\n"
"\n"
" type Person {\n"
" Person(name: String, age: Int, score: Float)\n"
" }\n"
"\n"
" let node_attrs = fn(p: Person) {\n"
" dict.from_list([\n"
" #(\"name\", #(p.name, StringType)),\n"
" #(\"age\", #(int.to_string(p.age), IntType)),\n"
" #(\"score\", #(float.to_string(p.score), DoubleType)),\n"
" ])\n"
" }\n"
"\n"
" let edge_attrs = fn(weight: Int) {\n"
" dict.from_list([\n"
" #(\"weight\", #(int.to_string(weight), DoubleType)),\n"
" ])\n"
" }\n"
"\n"
" let xml = graphml.serialize_with_types(node_attrs, edge_attrs, graph)\n"
" ```\n"
).
-spec serialize_with_types(
fun((AEWW) -> gleam@dict:dict(binary(), {binary(), attribute_type()})),
fun((AEWX) -> gleam@dict:dict(binary(), {binary(), attribute_type()})),
yog@model:graph(AEWW, AEWX)
) -> binary().
serialize_with_types(Node_attr, Edge_attr, Graph) ->
serialize_with_types_and_options(
Node_attr,
Edge_attr,
default_options(),
Graph
).
-file("src/yog_io/graphml.gleam", 249).
?DOC(" Serializes a graph to a GraphML string with custom options.\n").
-spec serialize_with_options(
fun((AEXE) -> gleam@dict:dict(binary(), binary())),
fun((AEXF) -> gleam@dict:dict(binary(), binary())),
graph_m_l_options(),
yog@model:graph(AEXE, AEXF)
) -> binary().
serialize_with_options(Node_attr, Edge_attr, Options, Graph) ->
serialize_internal(fun(N) -> _pipe = Node_attr(N),
gleam@dict:map_values(_pipe, fun(_, V) -> {V, string_type} end) end, fun(
E
) ->
_pipe@1 = Edge_attr(E),
gleam@dict:map_values(
_pipe@1,
fun(_, V@1) -> {V@1, string_type} end
)
end, Options, Graph, false).
-file("src/yog_io/graphml.gleam", 169).
?DOC(
" Serializes a graph to a GraphML string with custom attribute mappers.\n"
"\n"
" This function allows you to control how node and edge data are converted\n"
" to GraphML attributes. Use `serialize` for simple cases where node/edge\n"
" data are strings.\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/graphml\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([#(\"name\", 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 xml = graphml.serialize_with(node_attrs, edge_attrs, graph)\n"
" ```\n"
).
-spec serialize_with(
fun((AEWS) -> gleam@dict:dict(binary(), binary())),
fun((AEWT) -> gleam@dict:dict(binary(), binary())),
yog@model:graph(AEWS, AEWT)
) -> binary().
serialize_with(Node_attr, Edge_attr, Graph) ->
serialize_with_options(Node_attr, Edge_attr, default_options(), Graph).
-file("src/yog_io/graphml.gleam", 481).
?DOC(
" Serializes a graph to a GraphML string.\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 stored\n"
" as a \"label\" attribute.\n"
"\n"
" **Time Complexity:** O(V + E)\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" import yog/model.{Directed}\n"
" import yog_io/graphml\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 xml = graphml.serialize(graph)\n"
" // <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
" // <graphml xmlns=\"http://graphml.graphdrawing.org/xmlns\">\n"
" // <key id=\"label\" for=\"node\" attr.name=\"label\" attr.type=\"string\"/>\n"
" // <key id=\"weight\" for=\"edge\" attr.name=\"weight\" attr.type=\"string\"/>\n"
" // <graph id=\"G\" edgedefault=\"directed\">\n"
" // <node id=\"1\"><data key=\"label\">Alice</data></node>\n"
" // <node id=\"2\"><data key=\"label\">Bob</data></node>\n"
" // <edge source=\"1\" target=\"2\"><data key=\"weight\">5</data></edge>\n"
" // </graph>\n"
" // </graphml>\n"
" ```\n"
).
-spec serialize(yog@model:graph(binary(), binary())) -> binary().
serialize(Graph) ->
serialize_with(
fun(D) -> maps:from_list([{<<"label"/utf8>>, D}]) end,
fun(W) -> maps:from_list([{<<"weight"/utf8>>, W}]) end,
Graph
).
-file("src/yog_io/graphml.gleam", 506).
?DOC(
" Writes a graph to a GraphML file.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" import yog/model.{Directed}\n"
" import yog_io/graphml\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) = graphml.write(\"mygraph.graphml\", 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/graphml.gleam", 542).
?DOC(
" Writes a graph to a GraphML file with custom attribute mappers.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" import gleam/dict\n"
" import yog/model.{Directed}\n"
" import yog_io/graphml\n"
"\n"
" type Person {\n"
" Person(name: String, age: Int)\n"
" }\n"
"\n"
" let node_attrs = fn(p: Person) {\n"
" dict.from_list([#(\"name\", p.name), #(\"age\", int.to_string(p.age))])\n"
" }\n"
"\n"
" let graph =\n"
" model.new(Directed)\n"
" |> model.add_node(1, Person(\"Alice\", 30))\n"
"\n"
" let assert Ok(Nil) = graphml.write_with(\n"
" \"people.graphml\",\n"
" node_attrs,\n"
" fn(e) { dict.from_list([#(\"type\", e)]) },\n"
" graph\n"
" )\n"
" ```\n"
).
-spec write_with(
binary(),
fun((AEXW) -> gleam@dict:dict(binary(), binary())),
fun((AEXX) -> gleam@dict:dict(binary(), binary())),
yog@model:graph(AEXW, AEXX)
) -> {ok, nil} | {error, simplifile:file_error()}.
write_with(Path, Node_attr, Edge_attr, Graph) ->
Content = serialize_with(Node_attr, Edge_attr, Graph),
simplifile:write(Path, Content).
-file("src/yog_io/graphml.gleam", 584).
?DOC(
" Writes a graph to a GraphML file with typed attributes for Gephi compatibility.\n"
"\n"
" This function creates GraphML files with proper attribute types that work\n"
" seamlessly with Gephi for visualizations, layouts, and statistical analysis.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" import gleam/dict\n"
" import gleam/int\n"
" import yog/model.{Directed}\n"
" import yog_io/graphml.{DoubleType, IntType, StringType}\n"
"\n"
" type Person {\n"
" Person(name: String, age: Int, score: Float)\n"
" }\n"
"\n"
" let node_attrs = fn(p: Person) {\n"
" dict.from_list([\n"
" #(\"label\", #(p.name, StringType)),\n"
" #(\"age\", #(int.to_string(p.age), IntType)),\n"
" #(\"score\", #(float.to_string(p.score), DoubleType)),\n"
" ])\n"
" }\n"
"\n"
" let assert Ok(Nil) = graphml.write_with_types(\n"
" \"people.graphml\",\n"
" node_attrs,\n"
" fn(e) { dict.from_list([#(\"weight\", #(e, DoubleType))]) },\n"
" graph\n"
" )\n"
" ```\n"
).
-spec write_with_types(
binary(),
fun((AEYC) -> gleam@dict:dict(binary(), {binary(), attribute_type()})),
fun((AEYD) -> gleam@dict:dict(binary(), {binary(), attribute_type()})),
yog@model:graph(AEYC, AEYD)
) -> {ok, nil} | {error, simplifile:file_error()}.
write_with_types(Path, Node_attr, Edge_attr, Graph) ->
Content = serialize_with_types(Node_attr, Edge_attr, Graph),
simplifile:write(Path, Content).