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, inputs: dict[str, Any] | None = None, incident: dict[str, Any] | None = None
):
self.__context = context
self.__specials = {
"inputs": inputs if isinstance(inputs, dict) else {},
"incident": incident if isinstance(incident, dict) else {},
}
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
dx = self.__context
for prefix in self.__specials:
if prefix == key or (key.startswith(prefix) and key[len(prefix) : len(prefix) + 1] in (".", "(", "=")):
dx = self.__specials
break
return demisto.dt(dx, 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: str, 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.
"""
return self.__extract(template, extractor, dx, 0, None)[0] if template else ""
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:
return None
def stringify(value: Any) -> str:
if value is None or (isinstance(value, dict) and (not value)) or (isinstance(value, list) and (not value)):
return ""
if isinstance(value, bool):
return "true" if value else "false"
return str(value)
def main():
args = assign_params(**demisto.args())
try:
value = args.get("value")
prefix = args.get("prefix")
suffix = args.get("suffix")
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 + [""]
dx = args.get("ctx_data")
if dx and isinstance(dx, str):
dx = json.loads(dx)
dx = ContextData(context=dx, inputs=args.get("ctx_inputs"), incident=args.get("ctx_inc"))
formatter = Formatter(variable_markers[0], variable_markers[1], argToBoolean(args.get("keep_symbol_to_null", False)))
prefix = stringify(prefix)
if prefix:
value = stringify(formatter.build(prefix, extract_dt, dx)) + stringify(value)
suffix = stringify(suffix)
if suffix:
value = stringify(value) + stringify(formatter.build(suffix, extract_dt, dx))
value = [] if value is None else value
except Exception as err:
# Don't return an error by return_error() as this is transformer.
raise DemistoException(str(err))
return_results(value)
if __name__ in ("__builtin__", "builtins", "__main__"):
main()
README
Returns a string concatenated with given prefix & suffix which supports DT expressions.
Script Data
Name
Description
Script Type
python3
Tags
transformer, general, string
Cortex XSOAR Version
6.5.0
Inputs
Argument Name
Description
value
The text to be concatenated with prefix & suffix
prefix
A prefix to concat to the start of the argument
suffix
A prefix to concat to the end of the argument
ctx_data
Context Data: Input . (single dot) on `From previous tasks` to enable to extract the context data.
ctx_inputs
`inputs` context: Input ‘inputs’ (no quotation) on `From previous tasks` to enable ${inputs.} expression in DT.
ctx_inc
`demisto` context: Input ‘incident’ (no quotation) on `From previous tasks` to enable ${incident.} expression in DT.
variable_markers
The pair of start and end markers to bracket a variable name
keep_symbol_to_null
Set to true not to replace a value if the variable is null, otherwise false.
Outputs
There are no outputs for this script.
Getting Started
The transformer concatenates prefix and suffix which supports DT expressions to the string.
Examples
Build an email address from a user ID by appending a domain
Parameters
Argument Name
Value
Note
value
jdoe
prefix
suffix
@${domain}
ctx_data
.
Make sure that From previous tasks is selected
ctx_inputs
ctx_inc
variable_markers
keep_symbol_to_null
Context Data
{
"domain": "paloaltonetworks.com"
}
Output
jdoe@example.com
Build an email address by adding a user ID to the domain
Parameters
Argument Name
Value
Note
value
paloaltonetworks.com
prefix
${userid}@
suffix
ctx_data
.
Make sure that From previous tasks is selected
ctx_inputs
ctx_inc
variable_markers
keep_symbol_to_null
Context Data
{
"userid": "jdoe"
}
Output
jdoe@example.com
Change the variable start and end marker to the windows command shell style such as %name%
Parameters
Argument Name
Value
Note
value
paloaltonetworks.com
prefix
%userid%@
suffix
ctx_data
.
Make sure that From previous tasks is selected
ctx_inputs
ctx_inc
variable_markers
%,%
keep_symbol_to_null
Context Data
{
"userid": "jdoe"
}
Output
jdoe@example.com
Change the variable start and end marker to the UNIX shell style such as $name
Parameters
Argument Name
Value
Note
value
paloaltonetworks.com
prefix
$userid@
suffix
ctx_data
.
Make sure that From previous tasks is selected
ctx_inputs
ctx_inc
variable_markers
$
keep_symbol_to_null
Context Data
{
"userid": "jdoe"
}
Output
jdoe@example.com
Keep variable names if they are missing in the context
Parameters
Argument Name
Value
Note
value
paloaltonetworks.com
prefix
${userid}@
suffix
ctx_data
.
Make sure that From previous tasks is selected
ctx_inputs
ctx_inc
variable_markers
keep_symbol_to_null
true
Context Data
{
}
Output
${userid}@paloaltonetworks.com
Use DTs to build variables
Parameters
Argument Name
Value
Note
value
paloaltonetworks.com
prefix
${userid=val.toUpperCase()}@
suffix
ctx_data
.
Make sure that From previous tasks is selected
ctx_inputs
ctx_inc
variable_markers
keep_symbol_to_null
Context Data
{
"userid": "jdoe"
}
Output
jdoe@example.com
Use nested DTs to build variables
Parameters
Argument Name
Value
Note
value
John Doe
prefix
Hello,
suffix
. ${message-${messageID}}
ctx_data
.
Make sure that From previous tasks is selected
ctx_inputs
ctx_inc
variable_markers
keep_symbol_to_null
Context Data
{
"message-1": "This is a test message.",
"messageID": 1
}