SetWithTemplate
Set a value built by a template in context under the key you entered.
- Type
- python
- Pack
- CommonScripts
Source
from collections.abc import Callable
from typing import Any
import demistomock as demisto # noqa: F401
from CommonServerPython import * # noqa: F401
class ContextData:
def __init__(self, context: dict[str, Any] | None = None):
self.__context = context or demisto.context()
def __get_inputs_value(self, key: str) -> Any:
inputs = demisto.callingContext.get("context.PlaybookInputs") or {}
return demisto.dt(inputs, key)
def __get_incident_value(self, key: str) -> Any:
incident = demisto.incident()
val = demisto.dt(incident, key)
if val is None:
val = demisto.dt(incident.get("CustomFields") or {}, key)
return val
def __get_lists_value(self, key: str) -> Any:
if key.startswith("."):
key = key[1:]
name = key
ok, val = execute_command("getList", {"listName": name}, fail_on_error=False)
if not ok:
for sep in ["=", "(", "."]:
name = key.split(sep, maxsplit=1)[0]
ok, val = execute_command("getList", {"listName": name}, fail_on_error=False)
if ok:
break
else:
return None
val = demisto.dt(val, "." + key[len(name) :])
if isinstance(val, str):
try:
val = json.loads(val)
except json.JSONDecodeError:
pass
return val
def get(self, key: str | None = None) -> Any:
"""Get the context value
:param key: The dt expressions (string within ${}).
:return: The value.
"""
if not key:
return None
for prefix, handler in {
"inputs": ContextData.__get_inputs_value,
"incident": ContextData.__get_incident_value,
"lists": ContextData.__get_lists_value,
}.items():
if prefix == key or (key.startswith(prefix) and key[len(prefix) : len(prefix) + 1] in (".", "(", "=")):
return handler(self, key[len(prefix) :])
return demisto.dt(self.__context, key)
class Formatter:
def __init__(self, start_marker: str, end_marker: str, keep_symbol_to_null: bool):
if not start_marker:
raise ValueError("start-marker is required.")
self.__start_marker = start_marker
self.__end_marker = end_marker
self.__keep_symbol_to_null = keep_symbol_to_null
@staticmethod
def __is_end_mark(source: str, ci: int, end_marker: str) -> bool:
if end_marker:
return source[ci : ci + len(end_marker)] == end_marker
else:
c = source[ci]
if c.isspace():
return True
elif c.isascii():
return c != "_" and not c.isalnum()
else:
return False
def __extract(
self,
source: str,
extractor: Callable[[str, ContextData | None], Any] | None,
dx: ContextData | None,
si: int,
markers: tuple[str, str] | None,
) -> tuple[Any, int | None]:
"""Extract a template text, or an enclosed value within starting and ending marks
:param source: The template text, or the enclosed value starts with the next charactor of a start marker
:param extractor: The function to extract an enclosed value as DT
:param dx: The context data
:param si: The index of `source` to start extracting
:param markers: The start and end marker to find an end position for parsing an enclosed value.
It must be None when the template text is given to `source`.
:return: The extracted value and index of `source` when parsing ended.
The index is the next after the end marker when extracting the enclosed value.
"""
out = None
ci = si
while ci < len(source):
if markers is not None and Formatter.__is_end_mark(source, ci, markers[1]):
key = source[si:ci] if out is None else str(out) + source[si:ci]
if extractor:
if (xval := extractor(key, dx)) is None and self.__keep_symbol_to_null:
xval = markers[0] + key + markers[1]
else:
xval = key
return xval, ci + len(markers[1])
elif extractor and source[ci : ci + len(self.__start_marker)] == self.__start_marker:
xval, ei = self.__extract(
source, extractor, dx, ci + len(self.__start_marker), (self.__start_marker, self.__end_marker)
)
if si != ci:
out = source[si:ci] if out is None else str(out) + source[si:ci]
if ei is None:
xval = self.__start_marker
ei = ci + len(self.__start_marker)
if out is None:
out = xval
elif xval is not None:
out = str(out) + str(xval)
si = ci = ei
elif markers is None:
ci += 1
elif endc := {"(": ")", "{": "}", "[": "]", '"': '"', "'": "'"}.get(source[ci]):
_, ei = self.__extract(source, None, dx, ci + 1, (source[ci], endc))
ci = ci + 1 if ei is None else ei
elif source[ci] == "\\":
ci += 2
else:
ci += 1
if markers is not None:
# unbalanced braces, brackets, quotes, etc.
return None, None
elif not extractor:
return None, ci
elif si >= len(source):
return out, ci
elif out is None:
return source[si:], ci
else:
return str(out) + source[si:], ci
def build(self, template: Any, extractor: Callable[[str, ContextData | None], Any] | None, dx: ContextData | None) -> Any:
"""Format a text from a template including DT expressions
:param template: The template.
:param extractor: The extractor to get real value within ${dt}.
:param dx: The context instance.
:return: The text built from the template.
"""
if isinstance(template, dict):
return {self.build(k, extractor, dx): self.build(v, extractor, dx) for k, v in template.items()}
elif isinstance(template, list):
return [self.build(v, extractor, dx) for v in template]
elif isinstance(template, str):
return self.__extract(template, extractor, dx, 0, None)[0] if template else ""
else:
return template
def extract_dt(dtstr: str, dx: ContextData | None) -> Any:
"""Extract dt expression
:param dtstr: The dt expressions (string within ${}).
:param dx: The context instance.
:return: The value extracted.
"""
try:
return dx.get(dtstr) if dx else dtstr
except Exception as err:
demisto.debug(f'failed to extract dt from "{dtstr=}". Error: {err}')
return None
def normalize_value(value: Any, stringify: str) -> Any:
if stringify == "noop":
return value
elif stringify == "all":
if isinstance(value, dict):
# key should be str for the context
value = {k if isinstance(k, str) else json.dumps(k): normalize_value(v, stringify) for k, v in value.items()}
elif isinstance(value, list):
value = [normalize_value(v, stringify) for v in value]
elif not isinstance(value, str):
value = json.dumps(value)
elif stringify == "false":
if isinstance(value, str):
try:
value = json.loads(value)
except json.JSONDecodeError:
pass
elif stringify == "true":
if not isinstance(value, str):
value = json.dumps(value)
else:
raise DemistoException(f"Invalid stringify value: {stringify}")
return value
def main():
try:
args = assign_params(**demisto.args())
key = args.get("key")
template = args.get("template")
template_type = args.get("template_type", "raw")
append = argToBoolean(args.get("append", False))
stringify = args.get("stringify", "noop")
force = argToBoolean(args.get("force", False))
keep_symbol_to_null = argToBoolean(args.get("keep_symbol_to_null", False))
variable_markers = argToList(args.get("variable_markers", "${,}"))
if not variable_markers or not variable_markers[0]:
raise ValueError("variable_markers must have a start marker.")
elif len(variable_markers) >= 3:
raise ValueError("too many values for variable_markers.")
elif len(variable_markers) == 1:
variable_markers = variable_markers + [""]
value = ""
if template:
if template_type == "json":
template = json.loads(template)
elif template_type != "raw":
raise DemistoException(f"Invalid template type: {template_type}")
context = args.get("context")
if context:
context = json.loads(context) if isinstance(context, str) else context
else:
context = demisto.context()
formatter = Formatter(variable_markers[0], variable_markers[1], keep_symbol_to_null)
value = formatter.build(template, extract_dt, ContextData(context))
value = normalize_value(value, stringify)
if value or force:
readable_output = f"Key {key} set"
outputs = {key: value}
else:
readable_output = "value is None"
outputs = {}
if not append and outputs:
demisto.executeCommand("DeleteContext", {"key": key, "subplaybook": "auto"})
return_results(CommandResults(readable_output=readable_output, outputs=outputs))
except Exception as err:
return_error(str(err))
if __name__ in ("__builtin__", "builtins", "__main__"):
main()
README
Set a value built by a template in context under the key you entered.
Script Data
| Name | Description |
|---|---|
| Script Type | python3 |
| Tags | Utility |
| Cortex XSOAR Version | 6.5.0 |
Inputs
| Argument Name | Description |
|---|---|
| key | The key to set. Can be a full path such as “Key.ID”. If using append=true can also use a DT selector such as “Data(val.ID == obj.ID)”. |
| template | The template text which can include DT expressions such as ${value}. |
| template_type | The template type. |
| append | If false, the context key will be overwritten. If set to true, the script will be appended to the existing context key. |
| stringify | Whether to save the argument as a string. The default value is “noop”. |
| force | Whether to force the creation of the context. The default value is “false”. |
| context | The context data which overwrites the Demisto context. |
| variable_markers | The pair of start and end markers to bracket a variable name. |
| keep_symbol_to_null | Set to true to not replace a value if the variable is null, otherwise false. |
Outputs
There are no outputs for this script.
Getting Started
The script builds a text from a template text which includes variables such as:
- This is a test message for ${user_name}.
The template will be formatted to This is a test message for John Doe. by replacing variable parameters.
By default, a variable name starts with ${ and ends with } . You can change the start marker and end marker by specifying the variable_markers parameter.
Examples
Replace variables in a text based on the context data
Command
!SetWithTemplate key=out template=${lists.Template}
Lists Library
Template:
My name is ${first_name} ${last_name}.
Context Data
{
"first_name": "John",
"last_name": "Doe"
}
Output
{
"out": "My name is John Doe."
}
Change the variable start and end marker to the windows command shell style such as %name%
Command
!SetWithTemplate key=out template=${lists.Template} variable_markers=%,%
Lists Library
Template:
My name is %first_name% %last_name%.
Context Data
{
"first_name": "John",
"last_name": "Doe"
}
Output
{
"out": "My name is John Doe."
}
Change the variable start and end marker to the UNIX shell style such as $name
Command
!SetWithTemplate key=out template=${lists.Template} variable_markers=$
Lists Library
Template:
My name is $first_name $last_name.
Context Data
{
"first_name": "John",
"last_name": "Doe"
}
Output
{
"out": "My name is John Doe."
}
Keep variable names if they are missing in the context
Command
!SetWithTemplate key=out template=${lists.Template} keep_symbol_to_null=true
Lists Library
Template:
My name is ${first_name} ${last_name}.
Context Data
{
"first_name": "John"
}
Output
{
"out": "My name is John ${last_name}."
}
Use DTs to build variables
Command
!SetWithTemplate key=out template=${lists.Template}
Lists Library
Template:
My name is ${first_name=val.toUpperCase()} ${last_name=val.toUpperCase()}.
Context Data
{
"first_name": "John",
"last_name": "Doe"
}
Output
{
"out": "My name is JOHN DOE."
}