Files
acorderob-sd-webui-prompt-p…/docs/SYNTAX.md
T

40 KiB

Prompt PostProcessor syntax

Commands

The extension uses a format for its commands similar to an extranetwork, but it has a ppp: prefix followed by the command, and then a space and any parameters (if any).

<ppp:command parameters/>

When a command is associated with any content, it will be between an opening and a closing command:

<ppp:command parameters>content<ppp:/command>

For wildcards and choices it uses the formats from the Dynamic Prompts extension, but sometimes with some additional options for extra functionality.

Special characters

Remember that to use any special character (like parentheses, brackets or braces) as-is in the prompt (not as part of a construct), you need to escape them with a backslash. Like:

velma \(from scooby doo\)

And inside a yaml/json file that backslash must be itself escaped again to be preserved.

characters:
  - velma \\(from scooby doo\\)

Choices

The generic format is: {parameters$$opt1::choice1|opt2::choice2|opt3::choice3}

Both the construct parameters (up to the $$) and the individual choice options (up to the ::) are optional.

There is also a format where instead of parameters$$ you just put the sampler, for compatibility with Dynamic Prompts.

The construct parameters can be written with the following options (all are optional):

  • ~ (random) or @ (cyclical): sampler (for compatibility with Dynamic Prompts). The cyclical sampler cycles through all combinations in order across consecutive process_prompt calls, resuming where the previous call left off (as long as the input prompt and negative prompt do not change).
  • r: means it allows repetition of the choices.
  • o: means it is optional, and no error will be raised if there are no choices to select from.
  • n or n-m or n- or -m: number or range of choices to select. Allows zero as the start of a range. Default is 1.
  • 'description': optional description (quoted), only valid in wildcard definitions. Used only in the Wildcards Concat node in ComfyUI.
  • $$sep: separator when multiple choices are selected. Default is set in settings.
  • $$: end of the parameters (not optional if any parameters).

Regarding the optional flag, consider this scenario: due to their conditions no choice is available. It will raise an error. If you add the o then it will just return an empty string. This is only necessary if all choices have conditions and they could all be false. It is not the same as setting a range starting at 0, because that would be an allowed number of returned choices. If you do this and no choices are available, no error is raised.

The choice options are as follows:

  • %: indicates that the content of the choice is a command
  • 'identifiers': comma separated labels for the choice (optional, quotes can be single or double). Only makes sense inside a wildcard definition. Can be used when specifying the wildcard to select this specific choice. It's case insensitive.
  • n: weight of the choice (optional, default 1).
  • if condition: filters out the choice if the condition is false (optional; this is an extension to the Dynamic Prompts syntax). Same conditions as in the if command.
  • ::: end of choice options (not optional if any options)

Whitespace is allowed between parameters/options.

The only command available is include wildcard, which will include the choices of the specified wildcard in place of this choice. This allows composing choices from multiple wildcards. It also works in the choices of a wildcard, but note that in yaml you cannot start an array element with % and you will have to put the full choice in quotes, or use the object format.

These are examples of formats you can use to insert a choice construct:

Construct Result
{choice1|5::choice2|3::choice3} select 1 choice, two of them have weights
{3$$choice1|5 if _is_sd1::choice2|choice3} select 3 choices, one has a weight and a condition
{2-3$$2::choice1|choice2|choice3} select 2 to 3 choices, one of them has a weight
{r2-3$$choice1|choice2|choice3} select 2 to 3 choices allowing repetition
{2-3$$ / $$choice1|choice2|choice3} select 2 to 3 choices with separator /
{o$$if _is_sd1::choice1|if _is_sd2::choice2} select 1 choice, both have conditions, if none matches it is allowed because we indicate that it is optional
{choice1|choice2|%0.5::include path/wildcard} select 1 choice from the two specified and the ones inside the path/wildcard wildcard, which will be weighted with half their weights

Note

The Dynamic Prompts format {2$$__flavours__} does not work as expected because the wildcard is considered only one possible choice (it will only output one value). You can write it instead as a wildcard with parameters __2$$flavours__.

Whitespace around the choices is not ignored like in Dynamic Prompts, but will be cleaned up if the appropriate cleaning settings are selected.

Wildcards

The generic format is: __parameters$$wildcard'filter'(var=value)__

The parameters, the filter, and the setting of a variable are optional. The parameters follow the same format as for the choices.

Warning

Wildcards cannot be used inside an extranetwork tag (because some LoRA names contain double underscores). If you need to choose from multiple LoRAs put the whole extranetwork tag inside a wildcard, or use choices.

Identifier

  • Allowed characters are letters, numbers, underscore (_), dash (-), dot (.), and the path separators (/ and \). It cannot start with an underscore because it would be ambiguous whether it's part of the name or just precedes the wildcard.
  • Can have a relative path and contain globbing formatting, to read multiple wildcards and merge their choices. Note that if there are no parameters specified, the globbing will use the ones from the first wildcard that matches and have parameters (sorted by keys), so if you don't want that you might want to specify them. Also note that, unlike with Dynamic Prompts, the wildcard name has to be specified with its full path (unless you use globbing).
  • You can use variables, with the ${name}, ${name:default}, <ppp:echo name/> or <ppp:echo name>default<ppp:/>echo> formats, to build a dynamic identifier.

Filter

The filter can be used to filter specific choices from the wildcard. The filtering works before applying the choice conditions (if any). The surrounding quotes can be single or double.

The filter is a comma separated list of an integer/range (positional choice index, zero-based) or choice label. You can also compound them with +. That is, the comma separated items act as an OR and the + inside them as an AND. Using labels can simplify the definitions of complex wildcards where you want to have direct access to specific choices on occasion (you don't need to create wildcards for each individual choice). You can use variables for individual labels (${v}) or a single variable for the whole filter (${v} or ${a[&',']} or ${a[&'+']}).

There are some additional formats when using filters.

  • You can specify ^wildcard as a filter to use the filter of a previous wildcard in the chain.
  • You can start the filter (regular or inherited) with # and it will not be applied to the current wildcard choices, but the filter will remain in memory to use by other descendant wildcards. You use # and ^ when you want to pass a filter to inner wildcards (see the test files).

Variable

The variable value only applies during the evaluation of the selected choices and is discarded afterward (the variable keeps its original value if there was one).

Examples

These are examples of formats you can use to insert a wildcard:

Construct Result
__wildcard__ select 1 choice
__path/wildcard'0'__ select the first choice
__path/wildcard'1-2'__ select the second or third choice
__path/wildcard'label'__ select the choices with label label
__path/wildcard'0,label1,label2'__ select the first choice and those with labels label1 or label2
__path/wildcard'0,label1+label2'__ select the first choice and those with both labels label1 and label2
__3$$path/wildcard__ select 3 choices
__2-3$$path/wildcard__ select 2 to 3 choices
__r2-3$$path/wildcard__ select 2 to 3 choices allowing repetition
__2-3$$ / $$path/wildcard__ select 2 to 3 choices with separator /
__path/wildcard(var=value)__ select 1 choice using the specified variable value in the evaluation.

Wildcard definitions

A wildcard definition can be:

  • A txt file. The wildcard name will be the relative path of the file, without the extension. Each line will be a choice. Lines starting with # or empty are ignored. Doesn't support nesting.
  • An array or scalar value inside a json or yaml file. The wildcard name includes the relative folder path of the file, without the extension, but also the path of the value inside the file (if there is one). If the file contains a dictionary, the filename part is not used for the wildcard name. Supports nesting by having dictionaries inside dictionaries.

The best format is a yaml file with a dictionary of wildcards inside. An editor supporting yaml syntax and linting is recommended (f.e. VSCode).

In a choice, the content after a # is ignored.

If the first choice follows the format of wildcard parameters (including the final $$), it will be used as default parameters for that wildcard (see examples in the tests folder). Unless the only property used is the wildcard description.

The choices of the wildcard follow the same format as in the choices construct, or the object format of Dynamic Prompts (only in structured files).

If using the object format for a choice you can use the following in addition to the standard weight and text/content:

  • if: the condition (a string)
  • labels: list of labels (an array of strings)
  • command: indicates the content is a command (a boolean)
{ command: false, labels: ["some_label"], weight: 2, if: "_is_pony", content: "the text" } # "text" property can be used instead of "content"

Wildcard parameters in a json/yaml file can also be in object format, and support some additional properties, that are not included in the string format:

  • prefix: content to prefix the list of choices
  • suffix: content to suffix the list of choices
  • container: includes the prefix, choices array variable, and suffix
{ sampler: "~", repeating: false, optional: false, from: 2, to: 3, description: "test wildcard", container: "prefix-${__choices[&'/']}-suffix" }
{ sampler: "~", repeating: false, optional: false, count: 2, description: "test wildcard", prefix: "prefix-", suffix: "-suffix", separator: "/" }
{ sampler: "~", repeating: false, optional: false, from: 2, to: 3, description: "test wildcard", prefix: "prefix-", suffix: "-suffix", separator: "/" }

The prefix and suffix are added to the result along with the selected choices and separators. They can contain other constructs, but the separator can't.

The container is a new option that replaces prefix/suffix/separator, and makes use of the recent support for array variables. Its value would be the concatenation of any prefix and/or suffix with the echoing (with the chosen separator) of a temporary __choices[] variable that holds the chosen values. This property is preferred over prefix/suffix/separator unless you only need the separator.

It is recommended to use the object format for the wildcard parameters and for choices with complex options.

If your first choice is interpreted as parameters, and you don't need parameters, you can avoid the problem by adding an empty parameters object {} as first choice.

Wildcards can contain just one choice. In json and yaml formats this allows the use of a string value for the keys, rather than an array.

A choice inside a wildcard can also be a list or a dictionary of one element containing a list. These are considered anonymous wildcards. With a list it will be an anonymous wildcard with no choice options, and with a dictionary the key will be the options for the choice containing the anonymous wildcard and the value the choices of the anonymous wildcard. Anonymous wildcards can help formatting complex choice values that are used in only one place and thus creating a regular wildcard is not necessary. See test.yaml for examples. Use an anonymous wildcard to group options inside a wildcard, and attach a label to it to be able to choose only from that group.

Remember you can use the include command on choices to compose a wildcard from other wildcards' choices.

Important

The files should have UTF-8 encoding. The extension will also try with windows-1252 if that fails.

Wildcard definitions are reloaded automatically on each generation if they change.

Detection of remaining wildcards

Important

This extension should run after any other wildcard extensions, so if you don't use the internal wildcards processing, any remaining wildcards present in the prompt or negative_prompt at this point must be invalid. Usually you might not notice this problem until you check the image metadata, so this option gives you some ways to detect and treat the problem.

Set Wildcard Default Filter command

This command can be used to set a default filter for a wildcard, before it is used.

The format is:

Construct Meaning
<ppp:setwcdeffilter 'identifier' 'filter'/> Sets a filter
<ppp:setwcdeffilter 'identifier'/> Removes the filter

The wildcard identifier supports globbing. The filter does not allow the ^ or # flags.

Variables

The prompt has access to some system variables that contain model information, options, and other things. There is also the possibility of defining user variables.

Variable values true and false are considered a boolean, and numeric content is an integer or float.

All these variables can be used to output content or behave differently based on their values.

System variables

Names starting with an underscore are reserved for system variables:

System variable Value
_model the model identifier (sd1, sd2, sdxl, sd3, flux, auraflow). _sd also works but is deprecated.
_modelname the model filename (without path). Do not confuse with the modelname input in ComfyUI which matches actually to the _modelfullname variable. _sdname also works but is deprecated.
_modelfullname the model filename (with path). _sdfullname also works but is deprecated. In ComfyUI this variable can also be set to override the filename used for model detection (see below).
_modelclass the class used for the model. Note that this is dependent on the webui. In A1111 all SD versions use the same class. Can be used for new models that are not supported yet with the _is_* variables. The debug setting will show all system variables when generating in case you need to see which one to use for a certain model.
_is_kkkk true if the model is of kind kkkk (the model identifier, f.e. sdxl; those set in the ppp_config.yaml file)
_is_vvvv true if the model matches the vvvv model variant definition (based on its filename). Note that the corresponding variable for the model kind will also be true.
_is_pure_kkkk true if the model is of kind kkkk and not a variant.
_is_variant_kkkk true if the model version is any variant of model kind kkkk and not the pure version. Note that the corresponding variable for the model kind will also be true.
_is_sd true if the model is any version of SD
_is_ssd true if the model is SSD (Segmind Stable Diffusion 1B). Note that for an SSD model _is_sdxl will also be true.
_is_sdxl_no_ssd true if the model is SDXL and not an SSD model.
_is_sdxl_no_pony true if the model is SDXL and not a Pony model (the pony variant must be defined in settings). Kept to maintain compatibility with previous versions.
_opt_... All the options.
_input_seed The seed used.
_input_pos_prompt The original positive prompt.
_input_neg_prompt The original negative prompt.

Note

The model path is relative to the checkpoint/difussion_models folder, just as it appears in the load nodes.

Set command

This command sets the value of a variable that can be checked later.

The format is: <ppp:set varname [modifiers]>value<ppp:/set>

These are the available optional modifiers:

  • evaluate: the value of the variable is evaluated at this moment, instead of when it is used.
  • add: the value is "added" to the current value of the variable (depending on their type). When possible, it does not force an immediate evaluation of the old or added values.
  • ifundefined: the value will only be set if the variable is undefined.

The add and ifundefined modifiers are mutually exclusive and cannot be used together.

The Dynamic Prompts format also works:

Construct Meaning
${var=value} regular evaluation
${var=!value} immediate evaluation

If also supports the addition and undefined check as an extension of the Dynamic Prompts format:

Construct Meaning
${var+=value} equivalent to add
${var+=!value} equivalent to evaluate add
${var?=value} equivalent to ifundefined
${var?=!value} equivalent to evaluate ifundefined

Set variables are included in the output variables with their last value.

System variables cannot be set, except in some cases (see next point).

Overriding model information from the prompt (ComfyUI only)

In ComfyUI, _modelfullname can be set from the prompt using the set command or ${} syntax. Setting it triggers a re-evaluation of all _is_* system variables, so conditions that come later in the prompt will use the new values.

This is useful in workflows where the model information is not passed through the node inputs but is known at prompt time. And the _modelfullname variable can be extracted later by using the Select Variable node to actually load that model.

If a class is not specified because the model input is disconnected but the modelname is, it will try to detect the class from the file. This is the preferred way, or just set _modelfullname in the prompt.

The model filename is relative to the specific model folder (checkpoints, diffusion_models).

${_modelfullname=flux/fluxmodel.safetensors}

Warning

Attempting to set this variable in any other host will trigger a warning or error depending on the What to do on invalid content warnings? setting.

Echo command

This command prints the value of a variable, or the specified default if it doesn't exist. If the variable does not exist and has no default, depending on the What to do on invalid content warnings? setting it will assume an empty value, or result in an error.

The format is:

Construct
<ppp:echo varname/>
<ppp:echo varname>default<ppp:/echo>

The Dynamic Prompts format is:

Construct
${varname}
${varname:default}

An echoed variable that uses the default because it doesn't have a value will be included in the output variables with the last default value used.

Array variables

There is support for array variables. They use brackets [] to differenciate from regular variables.

They can be initialized in several ways:

Construct Meaning
${var[]=value} initialize and set the first value
${var[]=*()} initialize an empty array
${var[]=*var2[]} initialize an array from another array
${var[]=*__wildcard__} initialize an array from a wildcard
${var[]=*('value',var2)} initialize an array from a list of values (strings or variables)
${var[]+=} add an empty element to the array
${var[]+=value} add a value to the array
${var[]+=*var2[]} add elements from another array
${var[]+=*__wildcard__} add elements from a wildcard

The star operator * has always an inmediate evaluation.

And they can be accesed/echoed with:

  • Empty brackets mean the whole array (used when initializing or when echoing the whole array)
  • An integer inside the brackets means an indexed value. A variable identifier (not an indexed array) can be used to get the integer.
  • A hash inside the brackets is used to get the length of the array
  • An ampersand followed by a string inside the brackets (with quotes) is used to get the full array joined with a separator.
Construct Meaning
${var[]} echo all elements with a default separator
${var[&' / ']} echo all elements with a specific separator
${var[n]} echo an element from the array
${var[n]:default} echo an element with a default
${var[#]} echo the length of the array

If command

This command allows you to filter content based on conditions.

The full format is:

<ppp:if condition1>content one<ppp:elif condition2>content two<ppp:else>other content<ppp:/if>

Any elifs (there can be multiple) and the else are optional.

The conditionN is a boolean expression, which can use and, or, not and grouping with parentheses, and where the simplest expression can be:

Construct Meaning
operand check truthyness of the operand, meaning not zero, empty string nor empty array
operand1 [not] operation operand2 compare the operands

The operands can be a variable (variable, array[], array[index]), a quoted string, an integer, a boolean, or a parenthesized list of any of them ('string', 1, variable, false, array[], array[2]). The allowed operations depend on which operands are used.

When an operand is or contains a variable, it is resolved to the variable's current value before the operation.

String comparisons are case insensitive, and substring comparisons (see below) are always considered as strings.

The operation can be preceded by not for readability, instead of using it in the front.

The supported operations are: eq, ne, gt, lt, ge, le, in, any_in, contains and contains_any. The any_in and contains_any variants exist because any and contains consider all elements.

This list shows what they do depending on the kind of operand (R = regular variable, A = array variable).

Operation R1 op R2 A1 op A2 A1 op R2 R1 op A2
eq OK OK (pairwise) Error in strict mode, all A1 with R2 otherwise Error in strict mode, R1 with all A2 otherwise
ne OK OK (pairwise) Error in strict mode, all A1 with R2 otherwise Error in strict mode, R1 with all A2 otherwise
gt OK OK (pairwise) Error in strict mode, all A1 with R2 otherwise Error in strict mode, R1 with all A2 otherwise
lt OK OK (pairwise) Error in strict mode, all A1 with R2 otherwise Error in strict mode, R1 with all A2 otherwise
ge OK OK (pairwise) Error in strict mode, all A1 with R2 otherwise Error in strict mode, R1 with all A2 otherwise
le OK OK (pairwise) Error in strict mode, all A1 with R2 otherwise Error in strict mode, R1 with all A2 otherwise
in OK (substring, R1 in R2) OK (all A1 in A2) OK (all substring in A1 in R2) OK (R1 in A2)
any_in Error OK (any A1 in A2) OK (any substring in A1 in R2) Error
contains OK (substring, R2 in R1) OK (all A2 in A1) OK (R2 in A1) OK (all substrings A2 in R1)
contains_any Error OK (any A2 in A1) Error OK (any substrings A2 in R1)

When a comparison tries to compare undefined variables or the values have different types (f.e. an integer and a string), the behavior depends on the on_warning setting: in warn mode the comparison evaluates to false, and in stop mode an error is raised. In non strict mode a numeric string literal (with no leading zeros) will be considered an integer.

Example

(multiline to be easier to read)

<ppp:if _is_sd1><lora:test_sd1> test sd1x
<ppp:elif _sd_pony><lora:test_pony> test pony
<ppp:elif _sd_pure_sdxl><lora:test_sdxl> test sdxl
<ppp:else>unknown model
<ppp:/if>

Only one of the options will end up in the prompt, depending on the loaded model.

ExtraNetwork command

This command is a shortcut to add an extranetwork (usually a lora), and its triggers, with conditions. More legible and sometimes shorter than adding regular extranetworks inside if commands.

The full format is:

<ppp:ext type name [parameters] [if condition]>[triggers]<ppp:/ext> <ppp:ext type name [parameters] [if condition]/>

The type is the kind of extranetwork, like lora or hypernet.

The name is the extranetwork identifier. If it is not a regular identifier (i.e. starts with a number or contains spaces or symbols) it should be inside quotes.

The parameters is optional and its format depends on the extranetwork type. With LoRAs or HyperNets it is usually a single weight number, so if the type is one of those and there are no parameters it will default to 1. If it is not a number it should go inside quotes.

The condition uses the same format as in the if command, and it is also optional.

The triggers are also optional, and can be any content. If there are no triggers the command ending can be omitted.

If the condition passes (or if there is no condition) the extranetwork tag will be built and added to the result along with any triggers.

Examples

(multiline to be easier to read)

<ppp:ext lora test_sd1 if _is_sd1>test sd1x<ppp:/ext>
<ppp:ext lora test_pony 0.5 if _is_pony>test pony<ppp:/ext>
<ppp:ext lora test_ilxl if _is_illustrious/>
<ppp:ext lora 'test sdxl' '1:0.8' if _is_pure_sdxl>test sdxl<ppp:/ext>

Will turn into one of these (or none) depending on the model:

  • <lora:test_sd1:1>test sd1x
  • <lora:test_pony:0.5>test pony
  • <lora:test_illustrious:1>
  • <lora:test sdxl:1:0.8>test sdxl

Extranetworks mappings

The extranetwork command supports specifying mappings of extranetworks (like LoRAs), so, for example, a different one can be used depending on the loaded model.

If the type of extranetwork is prefixed with a $ the command will look for a mapping.

If you have LoRAs that do the same but for different models, create a mapping to group them, configuring there the weight and triggers for each one.

The mappings are configured in yaml files in any of the configured extranetwork mappings folders. The format is like this:

extnettype:
  mappingname:
    - condition: "<a supported condition>"
      name: "<name of the extranetwork>"
      parameters: "<parameters of the extranetwork>"
      triggers: [<list of triggers>]
      weight: 1.0
    ...

Used like this:

Construct Meaning
<ppp:ext $lora mappingname/> Mapping without additional triggers
<ppp:ext $lora mappingname>inline triggers<ppp:/ext> Mapping with additional triggers

Each mapping can have any number of elements in its list of mappings. There are no mandatory properties for a mapping. The properties mean the following:

  • extnettype: the kind of extranetwork, for example lora.
  • mappingname: the name you want to give to the mapping, to be referenced in the command.
  • condition: the condition to check for this mapping to be used (usually it should be one of the _is_* variables). If the conditions of multiple mappings evaluate to True, one will be chosen randomly. If the condition is missing it is considered True, to be used in the last mapping to catch as an else condition, and will be used if no other mapping applies.
  • name: name of the real extranetwork. If it is missing no extranetwork tag will be added.
  • parameters: parameters for the real extranetwork. If it is missing it is assumed 1 for LoRAs and HyperNets. If both this parameter and the parameter in the ext command are numbers they are multiplied for the result. In other case the parameter of the ext command, if it exists, is used.
  • triggers: list of trigger strings. If it is missing, only the inline triggers in the ext command will be added.
  • weight: weight for this variant, in case multiple of them apply, to choose one. Default is 1.

See the file in the tests folder as an example.

Sending content to the negative prompt

The new format for this command is like this:

Construct Meaning
<ppp:stn position>content<ppp:/stn> send to negative prompt
<ppp:stn iN/> insertion point to be used in the negative prompt as destination for the pN position

Where position is optional (defaults to the start) and can be:

  • s: at the start of the negative prompt
  • e: at the end of the negative prompt
  • pN: at the position of the insertion point in the negative prompt with N being 0-9. If the insertion point is not found it inserts at the start.

Example

You have a wildcard for hair colors (__haircolors__) with one being strawberry blonde, but you don't want strawberries. So in that option you add a command to add to the negative prompt, like so:

blonde
strawberry blonde <ppp:stn>strawberry<ppp:/stn>
brunette

Then, if that option is chosen this extension will process it later and move that part to the negative prompt.

Old format

The old format (<!...!>) is not supported anymore.

Notes

Positional insertion commands have less priority that start/end commands, so even if they are at the start or end of the negative prompt, they will end up inside any start/end (and default position) commands.

The content of the negative commands is not processed and is copied as-is to the negative prompt. Other modifiers around the commands are processed in the following way.

Attention modifiers (weights)

They will be translated to the negative prompt. For example:

  • (red<ppp:stn>square<ppp:/stn>:1.5) will end up as (square:1.5) in the negative prompt
  • (red[<ppp:stn>square<ppp:/stn>]:1.5) will end up as (square:1.35) in the negative prompt (weight=1.5*0.9) if the merge attention option is enabled or ([square]:1.5) otherwise.
  • (red<ppp:stn>[square]<ppp:/stn>:1.5) will also end up as (square:1.35) in the negative prompt if the merge attention option is enabled, because the content of the negative tag is a single attention construct whose weight is merged with the surrounding modifier.

Prompt editing constructs (alternation and scheduling)

Negative commands inside such constructs will copy the construct to the negative prompt, but separating its elements. For example:

  • Alternation: [red<ppp:stn>square<ppp:/stn>|blue<ppp:stn>circle<ppp:/stn>] will end up as [square|], [|circle] in the negative prompt, instead of [square|circle]
  • Scheduling: [red<ppp:stn>square<ppp:/stn>:blue<ppp:stn>circle<ppp:/stn>:0.5] will end up as [square::0.5], [:circle:0.5] instead of [square:circle:0.5]

This should still work as intended, and the only negative point i see is the unnecessary separators.