Difference between revisions of "Architecture - Available transformation functions"

From wiki.gpii
Jump to: navigation, search
m (category added again)
m
 
(27 intermediate revisions by 5 users not shown)
Line 1: Line 1:
 
= Introduction to transformations =
 
= Introduction to transformations =
  
The model transformation framework allows you to change an input model (JSON structure) based on a set of rules. The result will be a new JSON structure, built according to the rules specified and the input model.
+
The model transformation framework is part of [http://wiki.fluidproject.org/display/docs/Model+Transformation Fluid Infusion] and allows you to change an input model (JSON structure) based on a set of rules. The result will be a new JSON structure, built according to the rules specified and the input model.
  
 
As an example, imagine that you have a set of preferences specified in some format that is usable for application A and want to transform those into settings that are understandable by some other application B. To do this you would specify a set of rules, describing how the settings should be transformed from application A so it can be used by application B. More concretely
 
As an example, imagine that you have a set of preferences specified in some format that is usable for application A and want to transform those into settings that are understandable by some other application B. To do this you would specify a set of rules, describing how the settings should be transformed from application A so it can be used by application B. More concretely
  
 
imagine you have the following '''input model'' - settings defined in a way that application A understands them':
 
imagine you have the following '''input model'' - settings defined in a way that application A understands them':
<pre>
+
<pre>{
{
 
 
     "display": {
 
     "display": {
 
         "magnification": 0.8919,
 
         "magnification": 0.8919,
Line 15: Line 14:
  
 
And a set of '''transformation rules''', describing how to translate these settings into something compatible with application B:
 
And a set of '''transformation rules''', describing how to translate these settings into something compatible with application B:
<pre>
+
<pre>{
{
 
 
     "Magnification": {
 
     "Magnification": {
 
         "dataType": {
 
         "dataType": {
Line 38: Line 36:
  
 
Running the above transformation rules on the input model will result in the '''output document''':
 
Running the above transformation rules on the input model will result in the '''output document''':
<pre>
+
<pre>{
{
 
 
     "Magnification": {
 
     "Magnification": {
 
         "dataType": "REG_DWORD"
 
         "dataType": "REG_DWORD"
Line 48: Line 45:
  
 
In the below, the mechanics of the rules, detailed description of each of the transformations, and how the output document is created will be explained. The important thing to understand now is that the the model transformation system allows you to create an output document with some desired structure based on some input (the input model) and a set of rules (transformation rules). In common usage, your rules are likely to be a statically defined document, that can get applied to different input documents (with the same structure). So in the above example, we might have a different person with a different preference set (input model):
 
In the below, the mechanics of the rules, detailed description of each of the transformations, and how the output document is created will be explained. The important thing to understand now is that the the model transformation system allows you to create an output document with some desired structure based on some input (the input model) and a set of rules (transformation rules). In common usage, your rules are likely to be a statically defined document, that can get applied to different input documents (with the same structure). So in the above example, we might have a different person with a different preference set (input model):
 
 
<pre>{
 
<pre>{
 
     display: {
 
     display: {
Line 56: Line 52:
  
 
which, when run through the same transformation rules would result in different output document, namely:
 
which, when run through the same transformation rules would result in different output document, namely:
 
+
<pre>{
<pre>
 
{
 
 
     Magnification: {
 
     Magnification: {
 
         dataType: "REG_DWORD"
 
         dataType: "REG_DWORD"
Line 73: Line 67:
  
 
The simplest and most natural way to create the output structure is by using strings as keys in your output document. Returning to the rules set used in the previous section:
 
The simplest and most natural way to create the output structure is by using strings as keys in your output document. Returning to the rules set used in the previous section:
 
 
<pre>{
 
<pre>{
 
     "Magnification": {
 
     "Magnification": {
Line 98: Line 91:
 
Like with 'dataType', the "value" key under Magnification means that we change the output path for anything in that block. "transform" is a keywork, so this does not affect the output path. Nor do the keys directly in a "transform" block, meaning that "type" and "input" will not affect the output path. The same holds for the next transform blocks. This means that whatever the result of the rules inside the "value" block is, will get output to "Magnification.value" path.
 
Like with 'dataType', the "value" key under Magnification means that we change the output path for anything in that block. "transform" is a keywork, so this does not affect the output path. Nor do the keys directly in a "transform" block, meaning that "type" and "input" will not affect the output path. The same holds for the next transform blocks. This means that whatever the result of the rules inside the "value" block is, will get output to "Magnification.value" path.
  
Repeating the example above, an input model of:  
+
Repeating the example above, an input model of:
<pre>
+
<pre>{
{
 
 
     "display": {
 
     "display": {
 
         "magnification": 0.5,
 
         "magnification": 0.5,
Line 108: Line 100:
  
 
Running through the transformation will result in the following output document:
 
Running through the transformation will result in the following output document:
<pre>
+
<pre>{
{
 
 
     "Magnification": {
 
     "Magnification": {
 
         "dataType": "REG_DWORD"
 
         "dataType": "REG_DWORD"
Line 120: Line 111:
  
 
In general, the reserved words of the system (those that will not be interpreted as a change in output path) are:
 
In general, the reserved words of the system (those that will not be interpreted as a change in output path) are:
* "transform"
+
 
* "literalValue"
+
*"transform"
 +
*"literalValue"
  
 
Some transformations have further reserved words. These will be described for each relevant transformation.
 
Some transformations have further reserved words. These will be described for each relevant transformation.
Line 130: Line 122:
  
 
Returning to the rule set above, this could be rewritten as
 
Returning to the rule set above, this could be rewritten as
 
 
<pre>{
 
<pre>{
 
     "transform": [
 
     "transform": [
Line 152: Line 143:
  
 
Here, the "literalValue" transformation specifies that the result should be output to the path "Magnification.dataType". Same for the "fluid.transforms.round" transform, where the output will be sent to the path "Magnification.value". Like with the above, the input model:
 
Here, the "literalValue" transformation specifies that the result should be output to the path "Magnification.dataType". Same for the "fluid.transforms.round" transform, where the output will be sent to the path "Magnification.value". Like with the above, the input model:
 
+
<pre>{
<pre>
 
{
 
 
     "display": {
 
     "display": {
 
         "magnification": 0.5,
 
         "magnification": 0.5,
Line 162: Line 151:
  
 
Running through these rules will result in the following output document:
 
Running through these rules will result in the following output document:
<pre>
+
<pre>{
{
 
 
     "Magnification": {
 
     "Magnification": {
 
         "dataType": "REG_DWORD"
 
         "dataType": "REG_DWORD"
Line 172: Line 160:
  
 
It should be noted that the "outputPath" will be relative to the current output path specified via a string as key. So we could rewrite the above rule to:
 
It should be noted that the "outputPath" will be relative to the current output path specified via a string as key. So we could rewrite the above rule to:
 
 
<pre>{
 
<pre>{
 
     "Magnification": {
 
     "Magnification": {
Line 200: Line 187:
  
 
=== Return values ===
 
=== Return values ===
 +
 
In general, transformations pass their result one level up, unless "outputPath" is explicitly given. This is the reason that something like the below works:
 
In general, transformations pass their result one level up, unless "outputPath" is explicitly given. This is the reason that something like the below works:
 
 
<pre>{
 
<pre>{
 
     "transform": {
 
     "transform": {
Line 219: Line 206:
  
 
=== Arrays of transforms ===
 
=== Arrays of transforms ===
 +
 
Things get slighly more complicated when we introduce arrays of transforms, like in the examples from the previous section. Whenever you specify an array of transforms (ie. "transforms": [ (...) ]), nothing will get returned to the outer transform. The consequence of this is that if you have arrays of transforms, you ''are required to explicitly output'' values via "outputPath". Take the following rules, for example:
 
Things get slighly more complicated when we introduce arrays of transforms, like in the examples from the previous section. Whenever you specify an array of transforms (ie. "transforms": [ (...) ]), nothing will get returned to the outer transform. The consequence of this is that if you have arrays of transforms, you ''are required to explicitly output'' values via "outputPath". Take the following rules, for example:
 
 
<pre>{
 
<pre>{
 
     "Magnification": {
 
     "Magnification": {
Line 280: Line 267:
  
 
Would give the following result:
 
Would give the following result:
 
 
<pre>{
 
<pre>{
 
     "my_pets": [
 
     "my_pets": [
Line 298: Line 284:
  
 
As mentioned above, you can defined static values to the transformers by typing the values directly into the rules. For example:
 
As mentioned above, you can defined static values to the transformers by typing the values directly into the rules. For example:
 
 
<pre>{
 
<pre>{
 
     "my_pet": {
 
     "my_pet": {
Line 309: Line 294:
  
 
The result of this rule will be:
 
The result of this rule will be:
 
 
<pre>{
 
<pre>{
 
     "my_pet": "Kaspar the Titanic Cat"
 
     "my_pet": "Kaspar the Titanic Cat"
Line 321: Line 305:
  
 
For example, the below literalValue transformation gets its input from the path "petlist.cat" in the input model:
 
For example, the below literalValue transformation gets its input from the path "petlist.cat" in the input model:
 
 
<pre>{
 
<pre>{
 
     "my_pet": {
 
     "my_pet": {
Line 332: Line 315:
  
 
Given an input model of:
 
Given an input model of:
 
 
<pre>{
 
<pre>{
 
     "petlist": {
 
     "petlist": {
Line 340: Line 322:
  
 
The result of the transformation would be
 
The result of the transformation would be
 
 
<pre>{
 
<pre>{
 
     "my_pet": "Kaspar the Titanic Cat"
 
     "my_pet": "Kaspar the Titanic Cat"
Line 346: Line 327:
  
 
So here, the literal value is taken from the input model. If one was to run the transformation a second time, with a different input model, say:
 
So here, the literal value is taken from the input model. If one was to run the transformation a second time, with a different input model, say:
 
 
<pre>{
 
<pre>{
 
     "petlist": {
 
     "petlist": {
Line 354: Line 334:
  
 
The result of the transformation would instead be
 
The result of the transformation would instead be
 
 
<pre>{
 
<pre>{
 
     "my_pet": "CATTOO"
 
     "my_pet": "CATTOO"
Line 360: Line 339:
  
 
If no value is found in the given path, undefined will be returned from the transformation. So if we have an input model without the path "petlist.cat", like:
 
If no value is found in the given path, undefined will be returned from the transformation. So if we have an input model without the path "petlist.cat", like:
 
 
<pre>{
 
<pre>{
 
     "petlist": {
 
     "petlist": {
Line 368: Line 346:
  
 
The result of the literalValue transformation would be undefined, and one would recieve an empty output document.
 
The result of the literalValue transformation would be undefined, and one would recieve an empty output document.
 +
  
  
Line 373: Line 352:
  
 
As mentioned, it is possible to use both types of inputs in your ruleset in the different transforms, but it is also possible to specify both for a given transformation. The effect of this, is that the input from the model (via path) will be used if it is found, else the static value will be used. This is useful if you want to supply the system with a default value. Take for example the following rules:
 
As mentioned, it is possible to use both types of inputs in your ruleset in the different transforms, but it is also possible to specify both for a given transformation. The effect of this, is that the input from the model (via path) will be used if it is found, else the static value will be used. This is useful if you want to supply the system with a default value. Take for example the following rules:
 
 
<pre>{
 
<pre>{
 
     "my_pet": {
 
     "my_pet": {
Line 385: Line 363:
  
 
If we did the transformation with the following input model:
 
If we did the transformation with the following input model:
 
 
<pre>{
 
<pre>{
 
     "petlist": {
 
     "petlist": {
Line 393: Line 370:
  
 
The result of would be
 
The result of would be
 
 
<pre>{
 
<pre>{
 
     "my_pet": "Kaspar the Titanic Cat"
 
     "my_pet": "Kaspar the Titanic Cat"
Line 399: Line 375:
  
 
as this is what's read from the "petlist.cat" path. On the other hand, if we supplied it with the following model:
 
as this is what's read from the "petlist.cat" path. On the other hand, if we supplied it with the following model:
 
 
<pre>{
 
<pre>{
 
     "petlist": { }
 
     "petlist": { }
Line 405: Line 380:
  
 
the system would fail at finding any value at "petlist.cat", and instead use the value supplied via input, resulting in the output document:
 
the system would fail at finding any value at "petlist.cat", and instead use the value supplied via input, resulting in the output document:
 
 
<pre>{
 
<pre>{
 
     "my_pet": "I have no cat"
 
     "my_pet": "I have no cat"
 
}</pre>
 
}</pre>
 +
  
  
 
= Grades of transformations =
 
= Grades of transformations =
  
Transformation can have different grades (or classes), which will define how it handles inputs and outputs. A description of these will be given in the below
+
Transformation can be registered with different [http://docs.fluidproject.org/infusion/development/ComponentGrades.html grades] (or classes), which define how they handle inputs and outputs. The standard base grades recognised by the framework as as follows:
  
== standardInputTransformationFunction: ==
+
== standardInputTransformFunction: ==
  
 
These transformations takes an input which can be defined in two ways:
 
These transformations takes an input which can be defined in two ways:
Line 424: Line 399:
 
If both keys are used in an ''transform'' declaration, the value found at the ''inputPath'' will be used if something is found there. If not, the transformer will default to using the value defined by ''value''. Besides this input, the transformation might require an arbitrary number of constants given in the transform declaration.
 
If both keys are used in an ''transform'' declaration, the value found at the ''inputPath'' will be used if something is found there. If not, the transformer will default to using the value defined by ''value''. Besides this input, the transformation might require an arbitrary number of constants given in the transform declaration.
  
== standardOutputTransformationFunction: ==
+
== standardOutputTransformFunction: ==
  
 
These transformations only outputs a single value. The value of the output depends on the transformation, but the path to output to can be defined by:
 
These transformations only outputs a single value. The value of the output depends on the transformation, but the path to output to can be defined by:
Line 432: Line 407:
 
In case the transform is nested within other transforms (eg. a valuemapper), the outputPath specified will be relative to the outputPaths specified in the outer levels as well as to the location of the outermost transform.
 
In case the transform is nested within other transforms (eg. a valuemapper), the outputPath specified will be relative to the outputPaths specified in the outer levels as well as to the location of the outermost transform.
  
== standardTransformationFunction: ==
+
== standardTransformFunction: ==
  
These transformations satisfy the requirements of both standardInputTransformation and standardOutputTransformation (see above)
+
These transformations satisfy the requirements of both standardInputTransformFunction&nbsp;and standardOutputTransformFunction&nbsp;(see above)
  
== multiInputTransformationFunction: ==
+
== multiInputTransformFunction: ==
  
 
These transformations allows for multiple inputs. In their default blocks, besides the gradename, they require an "inputVariables" key. The value of this should be a hash of key-value pairs, where the key is the variable name and the value is the default value to assign to the variable in case no matching constant or path is found. For each of the variables declared, the system will look up the source-model path defined by &lt;variable&gt;Path in the transform and the &lt;variable&gt; declared in the transform. The path takes priority. Example:
 
These transformations allows for multiple inputs. In their default blocks, besides the gradename, they require an "inputVariables" key. The value of this should be a hash of key-value pairs, where the key is the variable name and the value is the default value to assign to the variable in case no matching constant or path is found. For each of the variables declared, the system will look up the source-model path defined by &lt;variable&gt;Path in the transform and the &lt;variable&gt; declared in the transform. The path takes priority. Example:
Line 448: Line 423:
 
});
 
});
 
</pre>
 
</pre>
 +
 
The above declaration defines three input variables; value, factor and offset. Taking as an example the factor, the first thing that will be looked up in the transform is the value of "factorPath". If it is defined, this path will be looked up in the source-model and this will be assigned to factor if found. If it's not found, the value (expanded if necessary) of the "factor" key of the transform will be used if found. If neither is found, the default value of 1 will be assigned to factor.
 
The above declaration defines three input variables; value, factor and offset. Taking as an example the factor, the first thing that will be looked up in the transform is the value of "factorPath". If it is defined, this path will be looked up in the source-model and this will be assigned to factor if found. If it's not found, the value (expanded if necessary) of the "factor" key of the transform will be used if found. If neither is found, the default value of 1 will be assigned to factor.
  
 
The inputVariables will be available to fluid.transforms.scaleValue as the first argument in the form of an object keyed by variable names with the values as described above.
 
The inputVariables will be available to fluid.transforms.scaleValue as the first argument in the form of an object keyed by variable names with the values as described above.
 
 
  
 
= Transformers =
 
= Transformers =
Line 472: Line 446:
  
  
=== Example 1: You can user literal value if you have some constant that you want to output to the document ===
+
 
 +
=== Example 1: You can use literalValue if you have some constant that you want to output to the document ===
 
<pre>transform: {
 
<pre>transform: {
 
     type: "fluid.transforms.literalValue",
 
     type: "fluid.transforms.literalValue",
Line 478: Line 453:
 
}
 
}
 
</pre>
 
</pre>
 +
 
'''Output:''' The output in this case would be:
 
'''Output:''' The output in this case would be:
 
<pre>"some constant"</pre>
 
<pre>"some constant"</pre>
 
  
 
=== Example 2: The content of literal value will need be transformed further ===
 
=== Example 2: The content of literal value will need be transformed further ===
Line 493: Line 468:
 
}
 
}
 
</pre>
 
</pre>
 +
 
'''Output:''' The content of value will just be output literally:
 
'''Output:''' The content of value will just be output literally:
 
<pre>transform: {
 
<pre>transform: {
Line 498: Line 474:
 
     input: "I'm not interpreted"
 
     input: "I'm not interpreted"
 
}</pre>
 
}</pre>
 +
  
  
Line 507: Line 484:
 
}
 
}
 
</pre>
 
</pre>
'''Output:''' if anything is found at the path "some.path" the content of that will be output literally, else "balloon" will be output
 
  
 +
'''Output''': if anything is found at the path "some.path" the content of that will be output literally, else "balloon" will be output.
 +
 +
== Value from a Path (fluid.transforms.value) ==
 +
 +
'''Syntax''':
 +
<pre>transform: {
 +
    "type": "fluid.transforms.value",
 +
    "inputPath": "some.path"
 +
}</pre>
 +
 +
'''Output''': The value found at "some.path" will be output.
  
 +
Shorthand version:
 +
<pre>"value": "some.path"</pre>
 +
 +
=== Examples of Full Form and Shorthands ===
 +
 +
The following syntax examples would have the same effect:
 +
<pre>capabilitiesTransformation: {
 +
    "highContrastEnabled": {
 +
        transform: {
 +
            "type": "fluid.transforms.value",
 +
            "inputPath": "display.screenEnhancement.-provisional-highContrastEnabled"
 +
        }
 +
    }
 +
}</pre>
 +
<pre>capabilitiesTransformation: {
 +
    "highContrastEnabled": {
 +
        "value": "display.screenEnhancement.-provisional-highContrastEnabled"
 +
    }
 +
}</pre>
 +
<pre>capabilitiesTransformation: {
 +
    "highContrastEnabled": "display.screenEnhancement.-provisional-highContrastEnabled"
 +
}</pre>
  
 
== To Array (fluid.transforms.arrayValue) ==
 
== To Array (fluid.transforms.arrayValue) ==
  
'''description''': This transformation will turn its value into an array if it's not already an array.
+
'''Description''': This transformation will turn its value into an array if it's not already an array.
  
'''Type''': standardTransformation Inputs:
+
'''Type''': standardTransformation
 +
 
 +
'''Inputs''':
  
 
*input / inputPath
 
*input / inputPath
Line 534: Line 545:
 
}
 
}
 
</pre>
 
</pre>
'''output''':  
+
 
<pre>
+
'''output''':
[ "some constant" ]
+
<pre>[ "some constant" ]
 
</pre>
 
</pre>
  
 
If the ''value'' had been an array already, it would just had been passed as is (so in the case - value: [ "some constant "] - we would have got the same output).
 
If the ''value'' had been an array already, it would just had been passed as is (so in the case - value: [ "some constant "] - we would have got the same output).
 
  
 
== Count length of array (fluid.transforms.count) ==
 
== Count length of array (fluid.transforms.count) ==
 
 
Type: standardTransformation Inputs:
 
  
 
'''Description:''' If the input is an array, the lenght of the array will be the output of this function. If the input is a primitive or object, 1 will be returned.
 
'''Description:''' If the input is an array, the lenght of the array will be the output of this function. If the input is a primitive or object, 1 will be returned.
Line 551: Line 558:
 
*input / inputPath
 
*input / inputPath
  
Syntax:
+
'''Type:'''&nbsp;standardTransformation Inputs:
<pre>  
+
 
transform: {
+
'''Syntax:'''
 +
<pre>transform: {
 
     type: "fluid.transforms.count",
 
     type: "fluid.transforms.count",
 
     value: <constant value>
 
     value: <constant value>
Line 559: Line 567:
 
</pre>
 
</pre>
  
Example:
+
=== Example 1: ===
 
<pre>  
 
<pre>  
 
transform: {
 
transform: {
Line 566: Line 574:
 
}
 
}
 
</pre>
 
</pre>
The above would give the value 2
+
 
 +
'''output''':
 +
<pre>[ "foo" ]</pre>
  
  
Line 572: Line 582:
 
== Get first value of array (fluid.transforms.firstValue) ==
 
== Get first value of array (fluid.transforms.firstValue) ==
  
Syntax:
+
'''Description:''' Will return the first entry of an array that does not evaluate to undefined. The input is required to be of type array.
<pre>  
+
 
transform: {
+
'''Type: '''fluid.transformFunction
 +
 
 +
'''Syntax:'''
 +
<pre>transform: {
 
     type: "fluid.transforms.firstValue",
 
     type: "fluid.transforms.firstValue",
     value: <constant of array type>
+
     values: <constant of array type>
 
}
 
}
 
</pre>
 
</pre>
Will return the first entry (that does not evaluate to undefined) of the array. The input is required to be of type array.
 
  
Example:
+
=== Example 1: ===
<pre>  
+
<pre>transform: {
transform: {
 
 
     type: "fluid.transforms.firstValue",
 
     type: "fluid.transforms.firstValue",
     value: [ "foo", "bar" ]
+
     values: [ undefined, "bar" ]
 
}
 
}
 
</pre>
 
</pre>
The above would give the value "foo".
 
  
 +
'''Output:'''
 +
<pre>bar</pre>
 +
 +
== Delete path from the output (fluid.transforms.delete) ==
  
 +
'''Description:''' This transformation will delete a path from the output document. This is useful when outputting a large structure to the output document, but you require deletion of a certain part of that structure.
  
== Delete path from the output (fluid.transforms.delete) ==
+
'''type:''' transformFunction
  
Syntax:
+
'''Syntax:'''
<pre>  
+
<pre>transform: {
transform: {
 
 
     type: "fluid.transforms.delete",
 
     type: "fluid.transforms.delete",
 
     outputPath: <the output path to delete>
 
     outputPath: <the output path to delete>
}
+
}</pre>
</pre>
 
Will delete the given path from the output.
 
  
Example:
+
=== Example 1 ===
<pre>  
+
<pre>{
{
 
 
     "": ""
 
     "": ""
 
     transform: {
 
     transform: {
Line 613: Line 624:
 
}
 
}
 
</pre>
 
</pre>
Given the model as input: { hello: "world", foo: "bar" }, the above would give the value { hello: "world" }. The "": "" in the transform would normally mean that the entire input model is copied without any transformations, but the delete transform ensures that the path "foo" is deleted.
 
  
 +
'''Input model:'''
 +
<pre>{
 +
    hello: "world",
 +
    foo: "bar"
 +
}</pre>
  
 +
'''Output:'''
 +
<pre>{
 +
    hello: "world"
 +
}</pre>
  
== Get first value of array (fluid.transforms.firstValue) ==
+
The "": "" in the transform would normally mean that the entire input model is copied without any transformations, but the delete transform ensures that the path "foo" is deleted.
 
 
Syntax:
 
<pre>
 
transform: {
 
    type: "fluid.transforms.firstValue",
 
    value: <constant of array type>
 
}
 
</pre>
 
Will return the first entry (that does not evaluate to undefined) of the array. The input is required to be of type array.
 
 
 
Example:
 
<pre>
 
transform: {
 
    type: "fluid.transforms.firstValue",
 
    value: [ "foo", "bar" ]
 
}
 
</pre>
 
The above would give the value "foo".
 
 
 
 
 
 
 
== Scale value with optional offset (fluid.transforms.linearScale) ==
 
 
 
type: multiInputTransformation (variables: value, factor offset)
 
 
 
Syntax:
 
<pre>
 
transform: {
 
    type: "fluid.transforms.scaleValue",
 
    value: <constant of array type>,
 
    factor: <the scaling factor>,
 
    offset: <the offset to use for the scaling>
 
}
 
</pre>
 
This will scale the input value using the equation: value * factor + offset. If factor is unspecified it will be interpreted as 1 and if offset is unspecified it will be interpreted as 0. Both factor and offset can references to the input model by using: factorPath and offsetPath, respectively. If both the path and constant for any of these values is defined, first the path is looked up, and if a value is found it will be used. Else the constant will be used.
 
 
 
Example:
 
<pre>
 
transform: {
 
    type: "fluid.transforms.scaleValue",
 
    value: 12,
 
    factor: 10,
 
    offsetPath: "some.path"
 
    offset: 100
 
}
 
</pre>
 
In this example, if no value is found at ''some.path'', the output will be 12*10+100 = 220. If the value 1000 is found at ''some.path'' the result would be 12*10+1000=1120.
 
 
 
Example:
 
<pre>transform: {
 
    type: "fluid.transforms.scaleValue",
 
    value: 12,
 
    factorPath: "some.path"
 
}
 
</pre>
 
In this example, if no value is found at ''some.path'', the factor will default to 1, outputting 12*1+0=12. If a value is found (say, 12) the output will be 12*12+0=144
 
 
 
 
 
  
 
== Binary operation (fluid.transforms.binaryOp) ==
 
== Binary operation (fluid.transforms.binaryOp) ==
Line 690: Line 651:
 
}
 
}
 
</pre>
 
</pre>
 +
 
This will do a binary operation given the two operands (left and right) and the operator. You can reference to the input model for both left and right by using ''leftPath'' and ''rightPath''. If both ''rightPath'' and ''right'' is given, a lookup will be done of ''rightPath'' first, and if nothing is found the constant from ''right'' will be used. Same goes for ''left'' and ''leftPath''. Both the left and right operands are required (either in their path or constant form). The ''operator'' is also required. The result of the expansion will be the result of the binary operation. Valid operands are:
 
This will do a binary operation given the two operands (left and right) and the operator. You can reference to the input model for both left and right by using ''leftPath'' and ''rightPath''. If both ''rightPath'' and ''right'' is given, a lookup will be done of ''rightPath'' first, and if nothing is found the constant from ''right'' will be used. Same goes for ''left'' and ''leftPath''. Both the left and right operands are required (either in their path or constant form). The ''operator'' is also required. The result of the expansion will be the result of the binary operation. Valid operands are:
  
Line 723: Line 685:
 
}
 
}
 
</pre>
 
</pre>
 +
 
The above would give the sum (a number) of 100 and the value found in the input model at the path "some.path".
 
The above would give the sum (a number) of 100 and the value found in the input model at the path "some.path".
  
Line 738: Line 701:
 
}
 
}
 
</pre>
 
</pre>
 +
 
Based on the boolean ''condition'' constant (or the path to the inputModel ''conditionPath'') either the value or ''true'' or ''false'' (truePath/falsePath, respectively) will be the result of the transform. The condition is required and either true or false (or both) - or their path equivalents - should be set.
 
Based on the boolean ''condition'' constant (or the path to the inputModel ''conditionPath'') either the value or ''true'' or ''false'' (truePath/falsePath, respectively) will be the result of the transform. The condition is required and either true or false (or both) - or their path equivalents - should be set.
  
Line 743: Line 707:
 
<pre>  
 
<pre>  
 
transform: {
 
transform: {
     type: "fluid.transforms.binaryOp",
+
     type: "fluid.transforms.condition",
 
     conditionPath: "some.path",
 
     conditionPath: "some.path",
 
     "true": "It was true",
 
     "true": "It was true",
Line 750: Line 714:
 
</pre>
 
</pre>
  
== Changing an object into an object (fluid.transforms.objectToArray) ==
+
== Scale value with optional offset (fluid.transforms.linearScale) ==
 +
 
 +
'''Description:''' This will scale the input value using the equation: value * factor + offset. If factor is unspecified it will be interpreted as 1 and if offset is unspecified it will be interpreted as 0. Both factor and offset can references to the input model by using: factorPath and offsetPath, respectively. If both the path and constant for any of these values is defined, first the path is looked up, and if a value is found it will be used. Else the constant will be used.&nbsp;
 +
 
 +
'''type:''' multiInputTransformation (variables: value, factor - default=1, offset - default=0)
 +
 
 +
'''Syntax:'''
 +
<pre>transform: {
 +
    type: "fluid.transforms.linearScale",
 +
    value: <constant of array type>,
 +
    factor: <the scaling factor>,
 +
    offset: <the offset to use for the scaling>
 +
}</pre>
 +
 
 +
 
 +
 
 +
=== Example 1: all variable specified ===
 +
 
 +
'''Rules:'''
 +
<pre>transform: {
 +
    type: "fluid.transforms.linearScale",
 +
    value: 12,
 +
    factor: 10,
 +
    offset: 100
 +
}
 +
</pre>
 +
 
 +
'''Output''':
 +
<pre>230</pre>
 +
 
 +
=== Example 2: Only some variables specified '''Rules''': ===
 +
<pre>transform: {
 +
    type: "fluid.transforms.linearScale",
 +
    value: 12,
 +
    factorPath: "some.path"
 +
}
 +
</pre>
 +
 
 +
'''Input Model''':
 +
<pre>{
 +
    hello: "say what?"
 +
}</pre>
 +
 
 +
'''Result''':
 +
<pre>12
 +
</pre>
 +
 
 +
In this example, if no value is found at ''some.path'', the factor will default to 1, outputting 12*1+0=12. If a value was instead found (say, 12) the output would've been 12*12+0=144
 +
 
 +
== Mapping a continuous range into discrete values (gpii.transformer.quantize) ==
 +
 
 +
'''Description:''' If you have a continuous range of values (eg. 0...*) and want to change that into discrete values, this is the transform you want. It also works as a non-linear scale of values, as you can define what ranges maps to what outputs.
 +
 
 +
The transform allows you to specify some '''ranges''', defined by an '''upperBound'''. The first entry, the range will be the upperBound value to negative infinite. For the second entry, the range will be the range from the upperBound to (and excluding) the previous entry's upperBound. For the final entry no upper bound can be given, indicating that this range is from the previous upperBound to infinity.
 +
 
 +
'''type:''' standardTransformFunction
 +
 
 +
'''Syntax:'''
 +
<pre>"transform": {
 +
    "type": "gpii.transformer.quantize",
 +
    "inputPath": <path to the value to check ranges from>,
 +
    "ranges": [
 +
        {
 +
            "upperBound": <the upper bound for first entry, lower bound is infinity>,
 +
            "output": <output value (or transform) in case the input falls into this range>
 +
        }, {
 +
            "upperBound": <the upper bound for second entry, lower bound is the 'upperBound' value from first entry>,
 +
            "output": <output value (or transform) in case the input falls into this range>
 +
        }, {
 +
            (...)
 +
        }, {
 +
            "output": <output value (or transform) if input is between previous upper bound and infinity>
 +
        }
 +
    ]
 +
}</pre>
 +
 
 +
=== Example 1: Usage of quantize transformer ===
 +
 
 +
'''Rules:'''
 +
<pre>transform: {
 +
    "type": "gpii.transformer.quantize",
 +
    "inputPath": "my.input",
 +
    "ranges": [
 +
        {
 +
            "upperBound": 11,
 +
            "output": "small"
 +
        }, {
 +
            "upperBound": 13,
 +
            "output": "normal"
 +
        }, {
 +
            "upperBound": 17,
 +
            "output": "big"
 +
        }, {
 +
            "output": "very big"
 +
        }
 +
    ]
 +
}
 +
 
 +
</pre>
 +
 
 +
'''Input Model''':
 +
<pre>{
 +
    hello: "12"
 +
}</pre>
 +
 
 +
'''result''':
 +
<pre>"normal"
 +
</pre>
 +
 
 +
The output here would be "normal", as 12 is larger than the upper bound of the first entry (11) but smaller than the upper bound of the second entry (13)... Other examples would be: Input 2 -> Output "small" Input 20 -> Output "very big" input 16 -> Output "big" Input 17 -> Output "big"
 +
 
 +
== Changing an object into an array (fluid.transforms.objectToArray) ==
  
 
== Changing an array into an object (fluid.transforms.arrayToObject) ==
 
== Changing an array into an object (fluid.transforms.arrayToObject) ==
Line 762: Line 837:
 
}
 
}
 
</pre>
 
</pre>
 +
 
As the name suggests, the arrayToObject transform changes an array into an object. This can be useful to be able to refer to a certain EL path of an input by a key which unlike reference to an array index does not depend on the order of the array. The transform requires a key - one that is present in each of the array-entries and unique - this will be used to key the resulting object. For example:
 
As the name suggests, the arrayToObject transform changes an array into an object. This can be useful to be able to refer to a certain EL path of an input by a key which unlike reference to an array index does not depend on the order of the array. The transform requires a key - one that is present in each of the array-entries and unique - this will be used to key the resulting object. For example:
  
Line 772: Line 848:
 
}
 
}
 
</pre>
 
</pre>
 +
 
And the transform
 
And the transform
 
<pre>{
 
<pre>{
Line 781: Line 858:
 
}
 
}
 
</pre>
 
</pre>
 +
 
We are saying that we want the array found on inputPath and return an object where where the key will be the value of product and the content will be the remainder of the array entry. So the above would return:
 
We are saying that we want the array found on inputPath and return an object where where the key will be the value of product and the content will be the remainder of the array entry. So the above would return:
 
<pre>{
 
<pre>{
Line 787: Line 865:
 
}
 
}
 
</pre>
 
</pre>
 +
 
An optional variable to the transform is the "innerValue". Any variable or transform that needs to refer to the content of the array should be declared here. The input paths within the innerValue block will be relative to the original array entry. (This behavior will be changed later with FLUID-XYZ).
 
An optional variable to the transform is the "innerValue". Any variable or transform that needs to refer to the content of the array should be declared here. The input paths within the innerValue block will be relative to the original array entry. (This behavior will be changed later with FLUID-XYZ).
  
Line 797: Line 876:
 
}
 
}
 
</pre>
 
</pre>
 +
 
And the transform
 
And the transform
 
<pre>{
 
<pre>{
Line 803: Line 883:
 
         inputPath: "foo.bar",
 
         inputPath: "foo.bar",
 
         key: "product",
 
         key: "product",
         inputPath: [{
+
         innerValue: [{
 
             transform: {
 
             transform: {
 
                 type: "fluid.transforms.value",
 
                 type: "fluid.transforms.value",
Line 813: Line 893:
 
}
 
}
 
</pre>
 
</pre>
 +
 
In the second (innermost) inputPath, we refer to info.healthy, which is relative to the path defined by our outer inputPath.
 
In the second (innermost) inputPath, we refer to info.healthy, which is relative to the path defined by our outer inputPath.
 
<pre>{
 
<pre>{
Line 837: Line 918:
 
}
 
}
 
</pre>
 
</pre>
 +
 
This transform can be used when one wants to create a set based on values available in an array. TODO: Add example and further descriptions. presentValue and missingValue are optional. If not defined, they'll default to true and false, respectively
 
This transform can be used when one wants to create a set based on values available in an array. TODO: Add example and further descriptions. presentValue and missingValue are optional. If not defined, they'll default to true and false, respectively
  
 +
== Using the valueMapper to convert to/from enumerated sets of values ==
 +
 +
As the name suggests, the valueMapper allows you to build a map of values that can be used in converting enumerated values to other forms of enumerations, or to literal values.
 +
 +
=== Converting from one set of enumerated values to another ===
 +
 +
We might want to convert from one acceptable list of options to another, for example, when translating a font preference between two systems.
 +
 +
Syntax:
 +
<pre>"transform": {
 +
                "type": "fluid.transforms.valueMapper",
 +
                "inputPath": "fontFace.genericFontFace",
 +
                "options": {
 +
                    "serif": "times",
 +
                    "sans serif": "verdana",
 +
                    "monospaced": "default",
 +
                    "fantasy": "default",
 +
                    "cursive": "default"
 +
                }
 +
</pre>
 +
 +
=== Converting from enumerated values to literal values using the valueMapper ===
 +
 +
Syntax:
 +
<pre>"transform": {
 +
    "type": "fluid.transforms.valueMapper",
 +
    "inputPath": "app1.volume",
 +
    "options": {
 +
        "high": {
 +
            "outputValue": {
 +
                "transform": {
 +
                    "type": "fluid.transforms.literalValue",
 +
                    "value": 80,
 +
                    "outputPath": "path"
 +
                }
 +
            }
 +
        },
 +
        "medium": {
 +
            "outputValue": {
 +
                "transform": {
 +
                    "type": "fluid.transforms.literalValue",
 +
                    "value": 50,
 +
                    "outputPath": "path"
 +
                }
 +
            }
 +
        },
 +
        "low": {
 +
            "outputValue": {
 +
                "transform": {
 +
                    "type": "fluid.transforms.literalValue",
 +
                    "value": 20,
 +
                    "outputPath": "path"
 +
                }
 +
            }
 +
        },
 +
        "off": {
 +
            "outputValue": {
 +
                "transform": {
 +
                    "type": "fluid.transforms.literalValue",
 +
                    "value": 0,
 +
                    "outputPath": "path"
 +
                }
 +
            }
 +
        }
 +
    }
 +
}</pre>
 +
 +
This transform would be useful to convert values like "low", "medium", "high" into numeric values.
  
==Wiki Categories==
+
== Wiki Categories ==
 
[[Category:GPII Architecture]]
 
[[Category:GPII Architecture]]

Latest revision as of 18:04, 21 September 2016

Contents

Introduction to transformations

The model transformation framework is part of Fluid Infusion and allows you to change an input model (JSON structure) based on a set of rules. The result will be a new JSON structure, built according to the rules specified and the input model.

As an example, imagine that you have a set of preferences specified in some format that is usable for application A and want to transform those into settings that are understandable by some other application B. To do this you would specify a set of rules, describing how the settings should be transformed from application A so it can be used by application B. More concretely

imagine you have the following 'input model - settings defined in a way that application A understands them':

{
    "display": {
        "magnification": 0.8919,
        "invert": true
    }
}

And a set of transformation rules, describing how to translate these settings into something compatible with application B:

{
    "Magnification": {
        "dataType": {
            "literalValue": "REG_DWORD"
        },
        "value": {
            "transform": {
                "type": "fluid.transforms.round",
                "input": {
                    "transform": {
                        "type": "fluid.transforms.linearScale",
                        "valuePath": "display.magnification",
                        "factor": 100
                    }
                }
            }
        }
    }
}

Running the above transformation rules on the input model will result in the output document:

{
    "Magnification": {
        "dataType": "REG_DWORD"
        "value": 89
    }
}

In the below, the mechanics of the rules, detailed description of each of the transformations, and how the output document is created will be explained. The important thing to understand now is that the the model transformation system allows you to create an output document with some desired structure based on some input (the input model) and a set of rules (transformation rules). In common usage, your rules are likely to be a statically defined document, that can get applied to different input documents (with the same structure). So in the above example, we might have a different person with a different preference set (input model):

{
    display: {
        magnification: 1,
    }
}

which, when run through the same transformation rules would result in different output document, namely:

{
    Magnification: {
        dataType: "REG_DWORD"
        value: 100
    }
}

Building the output structure

The ultimate goal when using the model transformation is to create an output document with some desired structure (and based on information in some input document). Therefore it's important to know how to build the output structure of the document. There are different ways of writing, and these will be explained below.

Strings as keys

The simplest and most natural way to create the output structure is by using strings as keys in your output document. Returning to the rules set used in the previous section:

{
    "Magnification": {
        "dataType": {
            "literalValue": "REG_DWORD"
        },
        "value": {
            "transform": {
                "type": "fluid.transforms.round",
                "input": {
                    "transform": {
                        "type": "fluid.transforms.linearScale",
                        "valuePath": "display.magnification",
                        "factor": 100
                    }
                }
            }
        }
    }
}

Here, the string output structure is defined using strings as keys, like "Magnification", "dataType" and "value". Whenever the transformation system encounters a key, that is a non-reserved word, and that is not inside a "transform" block, it will consider that a change in the output path / output structure. So the walking through the above rules, the first thing we encounter is "Magnification". Since this is not a reserved word, the system will ensure that everything within that block gets outputted relative to an output path "Magnification". The same goes for "dataType" - everything inside here will be output relative to the path "dataType" (which in turn is relative to Magnification, giving us the output path: "magnification.dataType"). "literalValue" inside dataType is a reserved word, so instead of interpreting this as a change to the output structure, it will be resolved by the model transformation system - a "literalValue" key, is a way to tell the system to literally print the value to the current output path, which as described above is at this location "Magnification.dataType".

Like with 'dataType', the "value" key under Magnification means that we change the output path for anything in that block. "transform" is a keywork, so this does not affect the output path. Nor do the keys directly in a "transform" block, meaning that "type" and "input" will not affect the output path. The same holds for the next transform blocks. This means that whatever the result of the rules inside the "value" block is, will get output to "Magnification.value" path.

Repeating the example above, an input model of:

{
    "display": {
        "magnification": 0.5,
        "invert": true
    }
}

Running through the transformation will result in the following output document:

{
    "Magnification": {
        "dataType": "REG_DWORD"
        "value": 50
    }
}

Reserved words

In general, the reserved words of the system (those that will not be interpreted as a change in output path) are:

  • "transform"
  • "literalValue"

Some transformations have further reserved words. These will be described for each relevant transformation.

Explictly outputting to a (relative) path

An alternative way to specifying where you want to output values is by using the "outputPath" key. The value of outputPath defines the location where you want to ouput the result of the transform. "outputPath" can only be specfied in a transform block (ie. at the same level as the "type" specification for the transform). "outputPath" works for most transformations (namely any standard output transformation (see section TODO)).

Returning to the rule set above, this could be rewritten as

{
    "transform": [
        {
            "type": "fluid.transforms.literalValue",
            "input": "REG_DWORD",
            "outputPath": "Magnification.dataType"
        }, {
            "type": "fluid.transforms.round",
            "input": {
                "transform": {
                    "type": "fluid.transforms.linearScale",
                    "valuePath": "display.magnification",
                    "factor": 100
                }
            },
            "outputPath": "Magnification.value"
        }
    ]
}

Here, the "literalValue" transformation specifies that the result should be output to the path "Magnification.dataType". Same for the "fluid.transforms.round" transform, where the output will be sent to the path "Magnification.value". Like with the above, the input model:

{
    "display": {
        "magnification": 0.5,
        "invert": true
    }
}

Running through these rules will result in the following output document:

{
    "Magnification": {
        "dataType": "REG_DWORD"
        "value": 50
    }
}

It should be noted that the "outputPath" will be relative to the current output path specified via a string as key. So we could rewrite the above rule to:

{
    "Magnification": {
        "transform": [
            {
                "type": "fluid.transforms.literalValue",
                "input": "REG_DWORD",
                "outputPath": "dataType"
            }, {
                "type": "fluid.transforms.round",
                "input": {
                    "transform": {
                        "type": "fluid.transforms.linearScale",
                        "valuePath": "display.magnification",
                        "factor": 100
                    }
                },
                "outputPath": "value"
            }
        ]
    }
}

and we would get the same result. That is, for example the "outputPath": "dataType", would be relative to the "Magnification" path, and hence result in the path "Magnification.dataType"

Return values, Arrays of transforms and arrays in transforms

Return values

In general, transformations pass their result one level up, unless "outputPath" is explicitly given. This is the reason that something like the below works:

{
    "transform": {
        "type": "fluid.transforms.round",
        "input": {
            "transform": {
                "type": "fluid.transforms.linearScale",
                "valuePath": "display.magnification",
                "factor": 100
            }
        },
        "outputPath": "Magnification.value"
    }
}

Here the result of the inner "fluid.transforms.linearScale" transform is passed to the "fluid.transforms.round" transform, which in turns outputs it to the "outputPath". Whenever an "outputPath" is specified, the result is output to the document instead, and hence not returned to the parent transform function (if any). Generally, this behavior should be rather intuitive.

Arrays of transforms

Things get slighly more complicated when we introduce arrays of transforms, like in the examples from the previous section. Whenever you specify an array of transforms (ie. "transforms": [ (...) ]), nothing will get returned to the outer transform. The consequence of this is that if you have arrays of transforms, you are required to explicitly output values via "outputPath". Take the following rules, for example:

{
    "Magnification": {
        "transform": [
            {
                "type": "fluid.transforms.literalValue",
                "input": "REG_DWORD",
                "outputPath": "dataType"
            }, {
                "type": "fluid.transforms.round",
                "input": {
                    "transform": {
                        "type": "fluid.transforms.linearScale",
                        "valuePath": "display.magnification",
                        "factor": 100
                    }
                },
                "outputPath": "value"
            }
        ]
    }
}

This works because we have specified the "outputPath" for both transforms. If we had omitted these, nothing would be output to the document.

Arrays in transforms

Outputting an array in the document is very straight forward, one simply specifies it, as one would normally in javascript/json - using []. The array will be populated with the results of the content as one would expect as well. If one of the array entries has a result of undefined (eg. due to an inputPath referencing something non-existant in input model -- see below), this would also populate a spot in the array - to ensure that indexes of results are consistent.

As an example, take the following set of rules

{
    "my_pets": [
        {
            "transform": {
                "type": "fluid.transforms.literalValue",
                "inputPath": "petlist.cat"
            }
        }, {
            "transform": {
                "type": "fluid.transforms.literalValue",
                "inputPath": "petlist.goldfish"
            }
        }, {
            "transform": {
                "type": "fluid.transforms.literalValue",
                "inputPath": "petlist.frog"
            }
        }
    ]
}

Running this transformation with the following input model:

{
    "petlist": {
        "cat": "Kaspar the Titanic Cat",
        "frog": "Portuguese Steve"
    }
}

Would give the following result:

{
    "my_pets": [
        "Kaspar the Titanic Cat",
        undefined,
        "Portuguese Steve"
    ]
}

Notice the undefined value. This is the result of the second transform (since no value was found at the "petlist.goldfish" input path, undefined is returned - this will be explained in the following section).

Static value vs. reading from input model

For a lot of transformations, there are two ways of providing inputs to be interpreted by the rules. One way is to pass a staic value, given directly in the rules document. This means that the result of the transformation in question will be the same every time. Alternatively, once can use inputs from the input document, by referencing the path where the value to be used can be found. When doing this, the result of the transformation will vary depending on the input document provided to the transformation. It is of course possible to use the two types of inputs in the rules

Static value

As mentioned above, you can defined static values to the transformers by typing the values directly into the rules. For example:

{
    "my_pet": {
        "transform": {
            "type": "fluid.transforms.literalValue",
            "input": "Kaspar the Titanic Cat"
        }
    }
}

The result of this rule will be:

{
    "my_pet": "Kaspar the Titanic Cat"
}

regardless of the input model supplied to the transformation. Every time the transformation is run, the result will be the same.

Reading inputs from input model

Obviously, to make model transformations useful, one needs to be able to get a different output document depending on the input document supplied to the transformations. One can read from the input paths by supplying it as the arguments to transformations.

For example, the below literalValue transformation gets its input from the path "petlist.cat" in the input model:

{
    "my_pet": {
        "transform": {
            "type": "fluid.transforms.literalValue",
            "inputPath": "petlist.cat"
        }
    }
}

Given an input model of:

{
    "petlist": {
        "cat": "Kaspar the Titanic Cat"
    }
}

The result of the transformation would be

{
    "my_pet": "Kaspar the Titanic Cat"
}

So here, the literal value is taken from the input model. If one was to run the transformation a second time, with a different input model, say:

{
    "petlist": {
        "cat": "CATTOO"
    }
}

The result of the transformation would instead be

{
    "my_pet": "CATTOO"
}

If no value is found in the given path, undefined will be returned from the transformation. So if we have an input model without the path "petlist.cat", like:

{
    "petlist": {
        "dog": "Spot"
    }
}

The result of the literalValue transformation would be undefined, and one would recieve an empty output document.


Using static values as default fallback value

As mentioned, it is possible to use both types of inputs in your ruleset in the different transforms, but it is also possible to specify both for a given transformation. The effect of this, is that the input from the model (via path) will be used if it is found, else the static value will be used. This is useful if you want to supply the system with a default value. Take for example the following rules:

{
    "my_pet": {
        "transform": {
            "type": "fluid.transforms.literalValue",
            "inputPath": "petlist.cat",
            "input": "I have no cat"
        }
    }
}

If we did the transformation with the following input model:

{
    "petlist": {
        "cat": "Kaspar the Titanic Cat"
    }
}

The result of would be

{
    "my_pet": "Kaspar the Titanic Cat"
}

as this is what's read from the "petlist.cat" path. On the other hand, if we supplied it with the following model:

{
    "petlist": { }
}

the system would fail at finding any value at "petlist.cat", and instead use the value supplied via input, resulting in the output document:

{
    "my_pet": "I have no cat"
}


Grades of transformations

Transformation can be registered with different grades (or classes), which define how they handle inputs and outputs. The standard base grades recognised by the framework as as follows:

standardInputTransformFunction:

These transformations takes an input which can be defined in two ways:

  • input: As a constant defined in the transform using the key input
  • inputPath: As a path reference to the input (model) by using the key inputPath.

If both keys are used in an transform declaration, the value found at the inputPath will be used if something is found there. If not, the transformer will default to using the value defined by value. Besides this input, the transformation might require an arbitrary number of constants given in the transform declaration.

standardOutputTransformFunction:

These transformations only outputs a single value. The value of the output depends on the transformation, but the path to output to can be defined by:

  • outputPath: An EL path to where the transformation should output it's value to.

In case the transform is nested within other transforms (eg. a valuemapper), the outputPath specified will be relative to the outputPaths specified in the outer levels as well as to the location of the outermost transform.

standardTransformFunction:

These transformations satisfy the requirements of both standardInputTransformFunction and standardOutputTransformFunction (see above)

multiInputTransformFunction:

These transformations allows for multiple inputs. In their default blocks, besides the gradename, they require an "inputVariables" key. The value of this should be a hash of key-value pairs, where the key is the variable name and the value is the default value to assign to the variable in case no matching constant or path is found. For each of the variables declared, the system will look up the source-model path defined by <variable>Path in the transform and the <variable> declared in the transform. The path takes priority. Example:

fluid.defaults("fluid.transforms.scaleValue", {
    gradeNames: [ "fluid.multiInputTransformFunction" ],
    inputVariables: { 
        value: null, 
        factor: 1,
        offset: 0
    }
});

The above declaration defines three input variables; value, factor and offset. Taking as an example the factor, the first thing that will be looked up in the transform is the value of "factorPath". If it is defined, this path will be looked up in the source-model and this will be assigned to factor if found. If it's not found, the value (expanded if necessary) of the "factor" key of the transform will be used if found. If neither is found, the default value of 1 will be assigned to factor.

The inputVariables will be available to fluid.transforms.scaleValue as the first argument in the form of an object keyed by variable names with the values as described above.

Transformers

Literal Value (fluid.transforms.literalValue)

Description: Returns the value given as input (or via inputPath), without attempting to do further transformations for that value. This transformation is basically an identity function - it will output the constant value given as input.

Type: standardTransformation Inputs:

  • input / inputPath 

Syntax:

transform: {
    type: "fluid.transforms.literalValue",
    input: <constant value>
}


Example 1: You can use literalValue if you have some constant that you want to output to the document

transform: {
    type: "fluid.transforms.literalValue",
    input: "some constant"
}

Output: The output in this case would be:

"some constant"

Example 2: The content of literal value will need be transformed further

transform: {
    type: "fluid.transforms.literalValue",
    input: {
       transform: {
          type: "fluid.transforms.helloworld",
          value: "I'm not interpreted"
       }
    }
}

Output: The content of value will just be output literally:

transform: {
    type: "fluid.transforms.helloworld",
    input: "I'm not interpreted"
}


Example 3: Like all transforms - will fall back from path to direct value

transform: {
    type: "fluid.transforms.literalValue",
    input: "balloon",
    inputPath: "some.path"
}

Output: if anything is found at the path "some.path" the content of that will be output literally, else "balloon" will be output.

Value from a Path (fluid.transforms.value)

Syntax:

transform: {
    "type": "fluid.transforms.value",
    "inputPath": "some.path"
}

Output: The value found at "some.path" will be output.

Shorthand version:

"value": "some.path"

Examples of Full Form and Shorthands

The following syntax examples would have the same effect:

capabilitiesTransformation: {
    "highContrastEnabled": {
        transform: {
            "type": "fluid.transforms.value",
            "inputPath": "display.screenEnhancement.-provisional-highContrastEnabled"
        }
    }
}
capabilitiesTransformation: {
    "highContrastEnabled": {
        "value": "display.screenEnhancement.-provisional-highContrastEnabled"
    }
}
capabilitiesTransformation: {
    "highContrastEnabled": "display.screenEnhancement.-provisional-highContrastEnabled"
}

To Array (fluid.transforms.arrayValue)

Description: This transformation will turn its value into an array if it's not already an array.

Type: standardTransformation

Inputs:

  • input / inputPath

Syntax:

 
transform: {
    type: "fluid.transforms.arrayValue",
    input: <constant value>
}

Example 1: Passing a single value will wrap it in an array

 
transform: {
    type: "fluid.transforms.arrayValue",
    input: "some constant"
}

output:

[ "some constant" ]

If the value had been an array already, it would just had been passed as is (so in the case - value: [ "some constant "] - we would have got the same output).

Count length of array (fluid.transforms.count)

Description: If the input is an array, the lenght of the array will be the output of this function. If the input is a primitive or object, 1 will be returned.

  • input / inputPath

Type: standardTransformation Inputs:

Syntax:

transform: {
    type: "fluid.transforms.count",
    value: <constant value>
}

Example 1:

 
transform: {
    type: "fluid.transforms.arrayValue",
    value: [ "foo", "bar" ]
}

output:

[ "foo" ]


Get first value of array (fluid.transforms.firstValue)

Description: Will return the first entry of an array that does not evaluate to undefined. The input is required to be of type array.

Type: fluid.transformFunction

Syntax:

transform: {
    type: "fluid.transforms.firstValue",
    values: <constant of array type>
}

Example 1:

transform: {
    type: "fluid.transforms.firstValue",
    values: [ undefined, "bar" ]
}

Output:

bar

Delete path from the output (fluid.transforms.delete)

Description: This transformation will delete a path from the output document. This is useful when outputting a large structure to the output document, but you require deletion of a certain part of that structure.

type: transformFunction

Syntax:

transform: {
    type: "fluid.transforms.delete",
    outputPath: <the output path to delete>
}

Example 1

{
    "": ""
    transform: {
        type: "fluid.transforms.delete",
        outputPath: foo
    }
}

Input model:

{ 
     hello: "world", 
     foo: "bar" 
}

Output:

{ 
     hello: "world"
}

The "": "" in the transform would normally mean that the entire input model is copied without any transformations, but the delete transform ensures that the path "foo" is deleted.

Binary operation (fluid.transforms.binaryOp)

type: multiInputTransformation (variables: left, right)

Syntax:

 
transform: {
    type: "fluid.transforms.binaryOp",
    left: <constant of appropriate type>,
    right: <constant of appropriate type>,
    operator: <the operator to use>
}

This will do a binary operation given the two operands (left and right) and the operator. You can reference to the input model for both left and right by using leftPath and rightPath. If both rightPath and right is given, a lookup will be done of rightPath first, and if nothing is found the constant from right will be used. Same goes for left and leftPath. Both the left and right operands are required (either in their path or constant form). The operator is also required. The result of the expansion will be the result of the binary operation. Valid operands are:

Arithmetic Operators (operands are required to be numbers, output will be a number):

  • +: (Addition) Adds 2 numbers.
  • -: (subtraction) As a unary operator, negates the value of its argument. As a binary operator, subtracts 2 numbers.
    • (Multiplication) Multiplies 2 numbers.
  • /: (Division) Divides 2 numbers.
  •  %: (Modulus) Computes the integer remainder of dividing 2 numbers.

Comparison Operators: (operands are required to be numbers, output will be boolean)

  • ===: Returns true if the operands are equal.
  •  !==: Returns true if the operands are not equal.
  • >: Returns true if left operand is greater than right operand.
  • >=: Returns true if left operand is greater than or equal to right operand.
  • <: Returns true if left operand is less than right operand.
  • <=: Returns true if left operand is less than or equal to right operand.

Logical Operators: (both operands are required to be booleans, output will be boolean)

  • &&: (Logical AND) Returns true if both logical operands are true. Otherwise, returns false.
  • ||: (Logical OR) Returns true if either logical expression is true. If both are false, returns false.

Example:

 
transform: {
    type: "fluid.transforms.binaryOp",
    left: 100,
    rightPath: "some.path",
    operator: "+"
}

The above would give the sum (a number) of 100 and the value found in the input model at the path "some.path".

Conditional transform (fluid.transforms.condition)

type: multiInputTransformation (variables: true, false, condition)

Syntax:

 
transform: {
    type: "fluid.transforms.condition",
    condition: <boolean value>
    "true": <can be declared in the transform as variable or path - optional>,
    "false": <can be declared in the transform as variable or path - optional>
}

Based on the boolean condition constant (or the path to the inputModel conditionPath) either the value or true or false (truePath/falsePath, respectively) will be the result of the transform. The condition is required and either true or false (or both) - or their path equivalents - should be set.

Example:

 
transform: {
    type: "fluid.transforms.condition",
    conditionPath: "some.path",
    "true": "It was true",
    "false": "It was false"
}

Scale value with optional offset (fluid.transforms.linearScale)

Description: This will scale the input value using the equation: value * factor + offset. If factor is unspecified it will be interpreted as 1 and if offset is unspecified it will be interpreted as 0. Both factor and offset can references to the input model by using: factorPath and offsetPath, respectively. If both the path and constant for any of these values is defined, first the path is looked up, and if a value is found it will be used. Else the constant will be used. 

type: multiInputTransformation (variables: value, factor - default=1, offset - default=0)

Syntax:

transform: {
    type: "fluid.transforms.linearScale",
    value: <constant of array type>,
    factor: <the scaling factor>,
    offset: <the offset to use for the scaling>
}


Example 1: all variable specified

Rules:

transform: {
    type: "fluid.transforms.linearScale",
    value: 12,
    factor: 10,
    offset: 100
}

Output:

230

Example 2: Only some variables specified Rules:

transform: {
    type: "fluid.transforms.linearScale",
    value: 12,
    factorPath: "some.path"
}

Input Model:

{
    hello: "say what?"
}

Result:

12

In this example, if no value is found at some.path, the factor will default to 1, outputting 12*1+0=12. If a value was instead found (say, 12) the output would've been 12*12+0=144

Mapping a continuous range into discrete values (gpii.transformer.quantize)

Description: If you have a continuous range of values (eg. 0...*) and want to change that into discrete values, this is the transform you want. It also works as a non-linear scale of values, as you can define what ranges maps to what outputs.

The transform allows you to specify some ranges, defined by an upperBound. The first entry, the range will be the upperBound value to negative infinite. For the second entry, the range will be the range from the upperBound to (and excluding) the previous entry's upperBound. For the final entry no upper bound can be given, indicating that this range is from the previous upperBound to infinity.

type: standardTransformFunction

Syntax:

"transform": {
    "type": "gpii.transformer.quantize",
    "inputPath": <path to the value to check ranges from>,
    "ranges": [
        {
            "upperBound": <the upper bound for first entry, lower bound is infinity>,
            "output": <output value (or transform) in case the input falls into this range>
        }, {
            "upperBound": <the upper bound for second entry, lower bound is the 'upperBound' value from first entry>,
            "output": <output value (or transform) in case the input falls into this range>
        }, {
            (...)
        }, {
            "output": <output value (or transform) if input is between previous upper bound and infinity>
        }
    ]
}

Example 1: Usage of quantize transformer

Rules:

transform: {
    "type": "gpii.transformer.quantize",
    "inputPath": "my.input",
    "ranges": [
        {
            "upperBound": 11,
            "output": "small"
        }, {
            "upperBound": 13,
            "output": "normal"
        }, {
            "upperBound": 17,
            "output": "big"
        }, {
            "output": "very big"
        }
    ]
}

Input Model:

{
    hello: "12"
}

result:

"normal"

The output here would be "normal", as 12 is larger than the upper bound of the first entry (11) but smaller than the upper bound of the second entry (13)... Other examples would be: Input 2 -> Output "small" Input 20 -> Output "very big" input 16 -> Output "big" Input 17 -> Output "big"

Changing an object into an array (fluid.transforms.objectToArray)

Changing an array into an object (fluid.transforms.arrayToObject)

Syntax:

transform: {
    type: "fluid.transforms.arrayToObject",
    inputPath: "some input path pointing to an array",
    key: "the variable from array to use as key"
    innerValue: [ (...inner transforms...) ]
}

As the name suggests, the arrayToObject transform changes an array into an object. This can be useful to be able to refer to a certain EL path of an input by a key which unlike reference to an array index does not depend on the order of the array. The transform requires a key - one that is present in each of the array-entries and unique - this will be used to key the resulting object. For example:

If we have the input model:

foo: {
    bar: [ 
        { product: "salad", price: 10, healthy: "yes" },
        { product: "candy", price: 18, healthy: "no" }
    ]
}

And the transform

{
    transform: {
        type: "fluid.transforms.arrayToObject",
        inputPath: "foo.bar",
        key: "product"
    }
}

We are saying that we want the array found on inputPath and return an object where where the key will be the value of product and the content will be the remainder of the array entry. So the above would return:

{
    salad: { price: 10, healthy: "yes" },
    candy: { price: 18, healthy: "no" }
}

An optional variable to the transform is the "innerValue". Any variable or transform that needs to refer to the content of the array should be declared here. The input paths within the innerValue block will be relative to the original array entry. (This behavior will be changed later with FLUID-XYZ).

As an example:

foo: {
    bar: [ 
        { product: "salad", info: { price: 10, healthy: "yes" }},
        { product: "candy", info: { price: 18, healthy: "no", tasty: "yes" }}
    ]
}

And the transform

{
    transform: {
        type: "fluid.transforms.arrayToObject",
        inputPath: "foo.bar",
        key: "product",
        innerValue: [{
            transform: {
                type: "fluid.transforms.value",
                inputPath: "info.healthy",
            }

        }]
    }
}

In the second (innermost) inputPath, we refer to info.healthy, which is relative to the path defined by our outer inputPath.

{
    salad: "yes",
    candy: "no"
}

Changing an array to a set (fluid.transforms.setMembershipToArray)

Changing an array to a set (fluid.transforms.arrayToSetMembership)

Syntax:

transform: {
    type: "fluid.transforms.arrayToOutputs",
    inputPath: "some input path pointing to an array",
    presentValue: "The value that the entry in the set should have if present in array",
    missingValue: "That value that the entry should have if not present in the array",
    options: {
        "expectedArrayEntry1": "Key of set1",
        (...)
        "expectedArrayEntryN": "Key of setN",
    }
}

This transform can be used when one wants to create a set based on values available in an array. TODO: Add example and further descriptions. presentValue and missingValue are optional. If not defined, they'll default to true and false, respectively

Using the valueMapper to convert to/from enumerated sets of values

As the name suggests, the valueMapper allows you to build a map of values that can be used in converting enumerated values to other forms of enumerations, or to literal values.

Converting from one set of enumerated values to another

We might want to convert from one acceptable list of options to another, for example, when translating a font preference between two systems.

Syntax:

"transform": {
                "type": "fluid.transforms.valueMapper",
                "inputPath": "fontFace.genericFontFace",
                "options": {
                    "serif": "times",
                    "sans serif": "verdana",
                    "monospaced": "default",
                    "fantasy": "default",
                    "cursive": "default"
                }

Converting from enumerated values to literal values using the valueMapper

Syntax:

"transform": {
    "type": "fluid.transforms.valueMapper",
    "inputPath": "app1.volume",
    "options": {
        "high": {
            "outputValue": {
                "transform": {
                    "type": "fluid.transforms.literalValue",
                    "value": 80,
                    "outputPath": "path"
                }
            }
        },
        "medium": {
            "outputValue": {
                "transform": {
                    "type": "fluid.transforms.literalValue",
                    "value": 50,
                    "outputPath": "path"
                }
            }
        },
        "low": {
            "outputValue": {
                "transform": {
                    "type": "fluid.transforms.literalValue",
                    "value": 20,
                    "outputPath": "path"
                }
            }
        },
        "off": {
            "outputValue": {
                "transform": {
                    "type": "fluid.transforms.literalValue",
                    "value": 0,
                    "outputPath": "path"
                }
            }
        }
    }
}

This transform would be useful to convert values like "low", "medium", "high" into numeric values.

Wiki Categories