Difference between revisions of "Architecture - Available transformation functions"

From wiki.gpii
Jump to: navigation, search
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.
 +
 +
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':
 +
<pre>
 +
{
 +
    display: {
 +
        magnification: 0.8919,
 +
        invert: true
 +
    }
 +
}</pre>
 +
 +
And a set of '''transformation rules''', describing how to translate these settings into something compatible with application B:
 +
<pre>
 +
{
 +
    Magnification: {
 +
        dataType: {
 +
            literalValue: "REG_DWORD"
 +
        },
 +
        value: {
 +
            "type": "fluid.transforms.round",
 +
            "input": {
 +
                "transform": {
 +
                    "type": "fluid.transforms.linearScale",
 +
                    "valuePath": "display.magnification",
 +
                    "factor": 100
 +
                }
 +
            }
 +
        }
 +
    }
 +
}
 +
</pre>
 +
 +
Running the above transformation rules on the input model will result in the '''output document''':
 +
<pre>
 +
{
 +
    Magnification: {
 +
        dataType: "REG_DWORD"
 +
        value: 89
 +
    }
 +
}
 +
</pre>
 +
 +
In the below, the mechanics of the rules 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>{
 +
    display: {
 +
        magnification: 1,
 +
    }
 +
}</pre>
 +
 +
which, when run through the same transformation rules would result in different output document, namely:
 +
 +
<pre>
 +
{
 +
    Magnification: {
 +
        dataType: "REG_DWORD"
 +
        value: 100
 +
    }
 +
}
 +
</pre>
  
 
= Outputting =
 
= Outputting =

Revision as of 16:44, 16 September 2013

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.

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: {
            "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 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
    }
}

Outputting

Direct value vs. reading from path

Grades of transformations

standardInputTransformationFunction:

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

  • value: As a constant defined in the transform using the key value
  • 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.

standardOutputTransformationFunction:

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.

standardTransformationFunction:

These transformations satisfy the requirements of both standardInputTransformation and standardOutputTransformation (see above)

multiInputTransformationFunction:

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 user literal value 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


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)

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.

  • input / inputPath

Syntax:

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

Example:

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

The above would give the value 2


Get first value of array

Syntax:

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

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

Example:

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

The above would give the value "foo".


Delete path from the output

Syntax:

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

Will delete the given path from the output.

Example:

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

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.


Get first value of array

Syntax:

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

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

Example:

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

The above would give the value "foo".


Scale value with optional offset

type: multiInputTransformation (variables: value, factor offset)

Syntax:

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

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:

 
transform: {
    type: "fluid.transforms.scaleValue",
    value: 12,
    factor: 10,
    offsetPath: "some.path"
    offset: 100
}

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:

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

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

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

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.binaryOp",
    conditionPath: "some.path",
    "true": "It was true",
    "false": "It was false"
}

Changing an array into an object

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",
        inputPath: [{
            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

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