Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
156 changes: 103 additions & 53 deletions clingraph/clingo_utils.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,29 @@
"""
Functions used for the clingo integration
"""

import json
import logging
import base64
import jsonschema
from clingo.control import Control
from clingo.script import enable_python
from clingo.symbol import String
from clingo.symbol import String, SymbolType
from jsonschema import validate
from .orm import Factbase
from .exceptions import InvalidSyntaxJSON, InvalidSyntax

enable_python()
log = logging.getLogger('custom')
log = logging.getLogger("custom")


class ClingraphContext:
"""
Provides avaliable python functions to be used in a visualization encoding
passed in the command line via option `--viz-encoding`
"""

def pos(self, x,y,scale=1):
def pos(self, x, y, scale=1):
"""
Position in the form of a tuple

Expand All @@ -31,8 +34,8 @@ def pos(self, x,y,scale=1):
(clingo.Symbol.String) position as a string of form (x,y)!
"""
scale = float(str(scale).strip('"'))
x = float(str(x))*scale
y = float(str(y))*scale
x = float(str(x)) * scale
y = float(str(y)) * scale
return String(f"{x},{y}!")

def concat(self, *args):
Expand All @@ -44,19 +47,39 @@ def concat(self, *args):
Returns:
(clingo.Symbol.String) The string concatenating all symbols
"""
return String(''.join([str(x).strip('"') for x in args]))
return String("".join([str(x).strip('"') for x in args]))

def replace(self, value, old, new):
"""
Replaces all occurrences of old by new in the given value
Args:
value (clingo.Symbol.String): The string where the replacement is done
old (clingo.Symbol.String): The substring to be replaced
new (clingo.Symbol.String): The substring to replace with
"""
return String(
str(value).strip('"').replace(str(old).strip('"'), str(new).strip('"'))
)

def format(self, s, *args):
"""
Formats the string with the given arguments

Args:
s (clingo.Symbol.String): The string to format, for example "{0} and {1}"
args: All symbols that can be accessed by the position starting in 0
args: All symbols that can be accessed by the position starting in 0.
If there is a single tuple as an argument, then its arguments are considered one by one.
Returns:
(clingo.Symbol.String) The string concatenating all symbols
"""
args_str = [str(v).strip('"') for v in args]
if (
len(args) == 1
and args[0].type == SymbolType.Function
and args[0].name == ""
):
args_str = [str(v).strip('"') for v in args[0].arguments]
else:
args_str = [str(v).strip('"') for v in args]
return String(s.string.format(*args_str))

def stringify(self, s, capitalize=False):
Expand All @@ -69,7 +92,7 @@ def stringify(self, s, capitalize=False):
(clingo.Symbol.String) The string
"""
val = str(s).strip('"')
val = val.replace('_',' ')
val = val.replace("_", " ")
if capitalize:
val = val[0].upper() + val[1:]
return String(val)
Expand All @@ -84,8 +107,7 @@ def cluster(self, s):
(clingo.Symbol.String) The string with the cluster name
"""
val = str(s).strip('"')
return String("cluster_"+val)

return String("cluster_" + val)

def html_escape(self, s):
"""
Expand All @@ -96,13 +118,28 @@ def html_escape(self, s):
Returns:
(clingo.Symbol.String) The string with the replacements
"""

return String(
str(s).strip('"')
.replace('&', '&')
.replace('"', '"')
.replace('<', '&lt;')
.replace('>', '&gt;'))
str(s)
.strip('"')
.replace("&", "&amp;")
.replace('"', "&quot;")
.replace("<", "&lt;")
.replace(">", "&gt;")
.replace(":", "&#58;")
)

def decodeB64(self, s): # pylint: disable=invalid-name
"""
Decodes a base 64 string

Args:
s (clingo.Symbol.String): A string in base 64
Returns:
(clingo.Symbol.String): The string decoded
"""
s = str(s)
decoded = base64.b64decode(s).decode("ascii")
return String(decoded)

def svg_init(self, property_name, property_value):
"""
Expand Down Expand Up @@ -149,7 +186,7 @@ def svg(self, event, element, property_name, property_value):
element = str(element).strip('"')
property_name = str(property_name).strip('"')
property_value = str(property_value).strip('"')
s=String(f"{event}___{element}___{property_name}___{property_value} ")
s = String(f"{event}___{element}___{property_name}___{property_value} ")
return s

def color(self, option, opacity=None):
Expand All @@ -175,7 +212,7 @@ def color(self, option, opacity=None):
"yellow": "#FFAB00",
"danger": "#FF5630",
"red": "#FF5630",
"light": "#F4F5F7"
"light": "#F4F5F7",
}
if option not in colors:
return String("#000000")
Expand All @@ -195,26 +232,25 @@ def clinguin_fontname(self):

return String("Helvetica Neue")



def __getattr__(self, name):
# pylint: disable=import-outside-toplevel

import __main__

return getattr(__main__, name)


clingo_json_schema = {
"type": "object",
"required": ["Call","Result"],
"properties":{
"required": ["Call", "Result"],
"properties": {
"Call": {
"type" : "array",
"type": "array",
},
"Result":{
"Result": {
"type": "string",
}
}
},
},
}


Expand All @@ -235,14 +271,20 @@ def parse_clingo_json(json_str):
try:
j = json.loads(json_str.encode())
validate(instance=j, schema=clingo_json_schema)
if j['Result'] == 'UNSATISFIABLE':
log.warning("Passing an unsatisfiable instance in the JSON. This wont produce any results")
if j["Result"] == "UNSATISFIABLE":
log.warning(
"Passing an unsatisfiable instance in the JSON. This wont produce any results"
)

if len(j["Call"]) > 1:
log.warning("Calls will multiple theads from clingo are not supported by clingraph")
log.warning(
"Calls will multiple theads from clingo are not supported by clingraph"
)

if not "Witnesses" in j["Call"][0]:
log.warning("No Witnesses (stable models) in the JSON output, no output will be produced by clingraph")
log.warning(
"No Witnesses (stable models) in the JSON output, no output will be produced by clingraph"
)
witnesses = []
else:
witnesses = j["Call"][0]["Witnesses"]
Expand All @@ -255,10 +297,12 @@ def parse_clingo_json(json_str):
return models_prgs

except json.JSONDecodeError as e:
raise InvalidSyntax('The json can not be read.',str(e)) from None
raise InvalidSyntax("The json can not be read.", str(e)) from None
except jsonschema.exceptions.ValidationError as e:
raise InvalidSyntaxJSON('The json does not have the expected structure. Make sure you used the -outf=2 option in clingo.',str(e)) from None

raise InvalidSyntaxJSON(
"The json does not have the expected structure. Make sure you used the -outf=2 option in clingo.",
str(e),
) from None


def _get_json(args, stdin):
Expand Down Expand Up @@ -320,14 +364,15 @@ def _get_json(args, stdin):
elem.addEventListener(event, function() {
console.log(event)
local_event = event;
class_name = local_event + "_" + elem.id;
var children = Object.values(document.getElementsByClassName(class_name));
elem_id = elem.id.split(' ').join('');
class_name = local_event + "___" + elem_id;
var children = elements.filter(el => Array.from(el.classList).some(cls => cls.startsWith(class_name)));
children.forEach(c => {
c.classList.forEach(c_elem =>{
c_vals = c_elem.split('___')
if (c_vals.length == 4){
if (c_vals[0]==local_event){
if(c_vals[1]==elem.id){
if(c_vals[1]==elem_id){
property = c_vals[2]
property_val = c_vals[3]
c.style[property]=property_val
Expand All @@ -348,18 +393,20 @@ def _get_json(args, stdin):
</svg>
"""


def add_svg_interaction_to_string(s):
"""
Adds the svg interaction script to string representation of the svg image

Args:
s [str]: the svg string
"""
s = s.replace("#111111","currentcolor")
s = s.replace("#111111", "currentcolor")
s = s[:-8]
s+= SVG_SCRIPT
s += SVG_SCRIPT
return s


def add_svg_interaction(paths):
"""
Adds the svg interaction script to a list of svg files defined in the paths.
Expand All @@ -373,12 +420,13 @@ def add_svg_interaction(paths):
if not path_dic:
continue
for path in path_dic.values():
with open(path, 'r', encoding='UTF-8') as f:
with open(path, "r", encoding="UTF-8") as f:
s = f.read()
s = add_svg_interaction_to_string(s)
with open(path, 'w', encoding='UTF-8') as f:
with open(path, "w", encoding="UTF-8") as f:
f.write(s)


ADD_IDS_PRG = """
#defined edge/2.
#defined edge/1.
Expand All @@ -394,48 +442,50 @@ def add_svg_interaction(paths):
attr(graph,ID,id,ID):-graph(ID,_).
"""


def add_elements_ids(ctl):
"""
Adds a program to the control that will set the ids of the elements to the id attribute
Args:
ctl Clingo.Control: The clingo control object that is used
"""

ctl.add("base",[],ADD_IDS_PRG)
ctl.add("base", [], ADD_IDS_PRG)


def _get_fbs_from_encoding(args,stdin,prgs_from_json):
def _get_fbs_from_encoding(args, stdin, prgs_from_json):
"""
Obtains the factbase by running clingo to compute the stable models
of a visualization encoding
"""
fbs = []

def add_fb_model(m):
fbs.append(Factbase.from_model(m,
prefix=args.prefix,
default_graph=args.default_graph))
fbs.append(
Factbase.from_model(m, prefix=args.prefix, default_graph=args.default_graph)
)

cl_args = ["-n1"]
if args.seed is not None:
cl_args.append(f'--seed={args.seed}')
cl_args.append(f"--seed={args.seed}")
if prgs_from_json is not None:
for prg in prgs_from_json:
ctl = Control(cl_args)
ctl.load(args.viz_encoding.name)
ctl.add("base",[],prg)
if args.format == 'svg':
ctl.add("base", [], prg)
if args.format == "svg":
add_elements_ids(ctl)
ctl.ground([("base", [])],ClingraphContext())
ctl.ground([("base", [])], ClingraphContext())
ctl.solve(on_model=add_fb_model)
else:
ctl = Control(cl_args)
ctl.load(args.viz_encoding.name)
ctl.add("base",[],stdin)
if args.format == 'svg':
ctl.add("base", [], stdin)
if args.format == "svg":
add_elements_ids(ctl)
for f in args.files:
ctl.load(f.name)
ctl.ground([("base", [])],ClingraphContext())
ctl.ground([("base", [])], ClingraphContext())
ctl.solve(on_model=add_fb_model)

return fbs
Loading
Loading