Module: Insika::Knowledge::GraphmlExport

Defined in:
lib/insika/knowledge.rb

Overview

The follow-up export shape (ยง5): one combined graph instead of one file per concept โ€” nodes are concepts (name/type/description/ confidence/provenance as node data), edges are [[links]] resolved against the SAME export set (a link to a concept outside the scope is dropped, never a dangling edge). Directly consumable by Gephi/yEd/ graphify. No new dependency: hand-built and hand-escaped XML, the same zero-dependency discipline the rest of this feature keeps.

Class Method Summary collapse

Class Method Details

.build(concepts) ⇒ Object

concepts: [Concept.parse output, ...]. -> String (one .graphml document).



633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
# File 'lib/insika/knowledge.rb', line 633

def build(concepts)
  names = concepts.map { |c| c[:name] }
  nodes = concepts.map { |c| node(c) }.join("\n")
  edges = concepts.flat_map { |c| edges_for(c, names) }.join("\n")
  <<~XML
    <?xml version="1.0" encoding="UTF-8"?>
    <graphml xmlns="http://graphml.graphdrawing.org/xmlns">
      <key id="name" for="node" attr.name="name" attr.type="string"/>
      <key id="type" for="node" attr.name="type" attr.type="string"/>
      <key id="description" for="node" attr.name="description" attr.type="string"/>
      <key id="confidence" for="node" attr.name="confidence" attr.type="double"/>
      <key id="provenance" for="node" attr.name="provenance" attr.type="string"/>
      <graph id="knowledge" edgedefault="directed">
    #{nodes}
    #{edges}
      </graph>
    </graphml>
  XML
end

.edges_for(concept, known_names) ⇒ Object



665
666
667
668
669
# File 'lib/insika/knowledge.rb', line 665

def edges_for(concept, known_names)
  Concept.links(concept[:body]).select { |name| known_names.include?(name) }.map do |target|
    %(    <edge source="#{escape(concept[:name])}" target="#{escape(target)}"/>)
  end
end

.escape(text) ⇒ Object

The five XML predefined entities โ€” no library needed for this subset. Order matters: & first, or every other substitution's own & gets re-escaped.



674
675
676
677
# File 'lib/insika/knowledge.rb', line 674

def escape(text)
  text.to_s.gsub("&", "&amp;").gsub("<", "&lt;").gsub(">", "&gt;")
      .gsub('"', "&quot;").gsub("'", "&apos;")
end

.node(concept) ⇒ Object



653
654
655
656
657
658
659
660
661
662
663
# File 'lib/insika/knowledge.rb', line 653

def node(concept)
  <<~NODE.chomp
        <node id="#{escape(concept[:name])}">
          <data key="name">#{escape(concept[:name])}</data>
          <data key="type">#{escape(concept[:type])}</data>
          <data key="description">#{escape(concept[:description])}</data>
          <data key="confidence">#{concept[:confidence]}</data>
          <data key="provenance">#{escape(concept[:provenance])}</data>
        </node>
  NODE
end