units.module in Units of Measurement 7.2
Same filename and directory in other branches
Provide API for managing and converting units of measurement.
File
units.moduleView source
<?php
/**
* @file
* Provide API for managing and converting units of measurement.
*/
/**
* Denote placeholder for quantity to use in decomposition of non-linear units.
*/
define('UNITS_QUANTITY', 'value');
/**
* Denote constant token type.
*
* @var int
*/
define('UNITS_TOKEN_TYPE_CONSTANT', 0);
/**
* Denote unit token type.
*
* @var int
*/
define('UNITS_TOKEN_TYPE_UNIT', 1);
/**
* Denote operator token type.
*
* @var int
*/
define('UNITS_TOKEN_TYPE_OPERATOR', 2);
/**
* Implements hook_entity_info().
*/
function units_entity_info() {
$entity_info = array();
$entity_info['units_unit'] = array(
'label' => t('Unit measurement'),
'entity class' => 'UnitsEntity',
'controller class' => 'EntityAPIControllerExportable',
'base table' => 'units_unit',
'fieldable' => TRUE,
'exportable' => TRUE,
'entity keys' => array(
'id' => 'umid',
'bundle' => 'measure',
'label' => 'label',
'name' => 'machine_name',
),
'bundles' => array(),
'bundle keys' => array(
'bundle' => 'measure',
),
'module' => 'units',
'access callback' => 'units_entity_access',
);
// We can't use here entity_load functions, nor EntityFieldQuery because
// entity info is not exposed to core yet.
$measures = db_select('units_measure', 'u_m')
->fields('u_m', array(
'measure',
'label',
))
->execute()
->fetchAllAssoc('measure');
foreach ($measures as $measure) {
$entity_info['units_unit']['bundles'][$measure->measure] = array(
'label' => $measure->label,
);
}
$entity_info['units_measure'] = array(
'label' => t('Measure'),
'entity class' => 'Entity',
'controller class' => 'EntityAPIControllerExportable',
'base table' => 'units_measure',
'fieldable' => FALSE,
'exportable' => TRUE,
'bundle of' => 'units_unit',
'entity keys' => array(
'id' => 'mid',
'label' => 'label',
'name' => 'measure',
),
'module' => 'units',
'access callback' => 'units_entity_access',
);
return $entity_info;
}
/**
* Implements hook_ctools_plugin_type().
*/
function units_ctools_plugin_type() {
$plugins = array();
$plugins['operator'] = array(
'defaults' => array(
'title' => NULL,
'description' => NULL,
'sign' => NULL,
'transparent operand1' => 0,
'transparent operand2' => 0,
'precedence' => NULL,
'dimension check' => TRUE,
'evaluate callback' => NULL,
'dimension callback' => NULL,
'split quantity callback' => NULL,
'isolate operand callback' => NULL,
'sql evaluate fragment' => NULL,
'operator class' => 'UnitsMathematicalOperatorLinear',
),
);
return $plugins;
}
/**
* Implements hook_ctools_plugin_directory().
*/
function units_ctools_plugin_directory($owner, $plugin_type) {
switch ($owner) {
case 'units':
switch ($plugin_type) {
case 'operator':
return 'plugins/' . $plugin_type;
}
break;
}
}
/**
* Implements hook_element_info().
*/
function units_element_info() {
return array(
'units_mathematical_expression' => array(
'#input' => TRUE,
'#process' => array(
'units_mathematical_expression_element_process',
),
'#element_validate' => array(
'units_mathematical_expression_element_validate',
),
// In what format to represent the submitted mathematical expression.
// Allowed values are:
// - object: to represent as an instance of
// UnitsMathematicalExpressionWrapper
// - infix: to represent as a string in infix notation
// - postfix: to represent as a string in postfix notation
'#value_format' => 'object',
// If the form element should accept only mathematical expressions of a
// specific dimension, put the allowed dimension here.
'#allowed_dimension' => NULL,
),
);
}
/**
* Access callback for entity types 'units_measure' and 'units_unit'.
*
* @param string $op
* The operation being performed. One of 'view', 'update', 'create' or
* 'delete'
* @param object $entity
* Entity object on which the operation is requested to be performed
* @param object $account
* Fully loaded user object of the account who requests to perform the
* operation
* @param string $entity_type
* Entity type on which the operation is requested to be performed
*
* @return bool
* Whether access has been granted
*/
function units_entity_access($op, $entity, $account, $entity_type) {
// There is no reason why we would limit access to 'units_measure' or
// 'units_unit' entities.
return TRUE;
}
/**
* Implements hook_ENTITY_TYPE_load().
*/
function units_units_unit_load($entities) {
// TODO: it should be replace with a batch loading all involved mathematical
// expressions in a single SQL SELECT.
foreach ($entities as $entity) {
$entity->decomposition = $entity->decomposition_mathematical_expression_id ? units_mathematical_expression_load($entity->decomposition_mathematical_expression_id) : NULL;
}
}
/**
* Implements hook_ENTITY_TYPE_presave().
*/
function units_units_unit_presave($entity) {
if (isset($entity->original->decomposition_mathematical_expression_id)) {
units_mathematical_expression_delete($entity->original->decomposition_mathematical_expression_id);
}
if (is_object($entity->decomposition)) {
$entity->decomposition
->save();
$entity->decomposition_mathematical_expression_id = $entity->decomposition
->getMathematicalExpressionId();
}
else {
$entity->decomposition_mathematical_expression_id = 0;
}
// We must make sure that $entity dimension is the same as all other units
// within its measure. It just does not make sense to have units of different
// dimension in the same measure.
$dimension = $entity
->dimension();
foreach (units_unit_by_measure_load_multiple($entity->measure) as $unit) {
if ($entity
->identifier() != $unit
->identifier() && !units_dimension_equal($dimension, $unit
->dimension())) {
// TODO: what am I supposed to do? throw an exception? show an error message? Log it to watchdog?
}
}
}
/**
* Implements hook_ENTITY_TYPE_delete().
*/
function units_units_unit_delete($entity) {
units_mathematical_expression_delete($entity->decomposition_mathematical_expression_id);
}
/**
* Implements hook_ENTITY_TYPE_delete().
*/
function units_units_measure_delete($entity) {
// Additionally delete units defined in the measure that is being deleted.
$ids = array();
foreach (units_unit_by_measure_load_multiple($entity) as $unit) {
$tmp = entity_extract_ids('units_unit', $unit);
$ids[] = $tmp[0];
}
units_unit_delete_multiple($ids);
}
/**
* Implements hook_ENTITY_TYPE_update().
*/
function units_units_measure_update($entity) {
if ($entity->measure != $entity->original->measure) {
db_update('units_unit')
->condition('measure', $entity->original->measure)
->fields(array(
'measure' => $entity->measure,
))
->execute();
}
}
/**
* Load an entity of entity type 'units_unit' by its ID.
*/
function units_unit_load($umid, $reset = FALSE) {
$units = units_unit_load_multiple(array(
$umid,
), array(), $reset);
return reset($units);
}
/**
* Load multiple entities of entity type 'units_unit'.
*/
function units_unit_load_multiple($umids = FALSE, $conditions = array(), $reset = FALSE) {
return entity_load('units_unit', $umids, $conditions, $reset);
}
/**
* Load a single entity of type 'units_unit' loading by its machine name.
*
* @param string $machine_name
* Machine name of entity to load
*
* @return object|bool
* Return fully loaded entity object if it was found, otherwise FALSE
*/
function units_unit_machine_name_load($machine_name) {
$query = new EntityFieldQuery();
$result = $query
->entityCondition('entity_type', 'units_unit')
->propertyCondition('machine_name', $machine_name)
->execute();
if (isset($result['units_unit'])) {
$keys = array_keys($result['units_unit']);
$entity_id = array_pop($keys);
return units_unit_load($entity_id);
}
// No entity was found.
return FALSE;
}
/**
* Load all units of the supplied measure.
*
* @param mixed $measure
* Either ID of the measure,
* or machine-readable name of the measure
* or fully loaded 'units_measure' entity object
*
* @return array
* Array of fully loaded 'units_unit' entity objects that belong to the
* supplied $measure
*/
function units_unit_by_measure_load_multiple($measure) {
// Trying to load entity object of $measure, if we were not supplied with one.
if (is_numeric($measure)) {
$measure = units_measure_load($measure);
}
elseif (!is_object($measure)) {
$measure = units_measure_machine_name_load($measure);
}
if (!is_object($measure)) {
// Probably we were supplied with bad parameter $measure, because at this
// point we are already supposed to have fully loaded 'units_measure' entity
// object.
return array();
}
$bundle = field_extract_bundle('units_unit', $measure);
$efq = new EntityFieldQuery();
$result = $efq
->entityCondition('entity_type', 'units_unit')
->entityCondition('bundle', $bundle)
->execute();
return isset($result['units_unit']) ? units_unit_load_multiple(array_keys($result['units_unit'])) : array();
}
/**
* Save an entity of type 'units_unit'.
*/
function units_unit_save($entity) {
entity_save('units_unit', $entity);
}
/**
* Delete a single entity of type 'units_unit'.
*/
function units_unit_delete($entity) {
entity_delete('units_unit', entity_id('units_unit', $entity));
}
/**
* Delete multiple entities of type 'units_unit'.
*
* @param array $umids
* Array of entity ids to be deleted
*/
function units_unit_delete_multiple($umids) {
entity_delete_multiple('units_unit', $umids);
}
/**
* Load an entity of entity type 'units_measure' by its ID.
*/
function units_measure_load($mid, $reset = FALSE) {
$measures = units_measure_load_multiple(array(
$mid,
), array(), $reset);
return reset($measures);
}
/**
* Load multiple entities of entity type 'units_unit'.
*/
function units_measure_load_multiple($mids = FALSE, $conditions = array(), $reset = FALSE) {
return entity_load('units_measure', $mids, $conditions, $reset);
}
/**
* Load a single entity of type 'units_measure' loading by its machine name.
*
* @param string $machine_name
* Machine name of entity to load
*
* @return object|bool
* Return fully loaded entity object if it was found, otherwise FALSE
*/
function units_measure_machine_name_load($machine_name) {
$query = new EntityFieldQuery();
$result = $query
->entityCondition('entity_type', 'units_measure')
->propertyCondition('measure', $machine_name)
->execute();
if (isset($result['units_measure'])) {
$keys = array_keys($result['units_measure']);
$entity_id = array_pop($keys);
return units_measure_load($entity_id);
}
// No entity was found.
return FALSE;
}
/**
* Save an entity of type 'units_measure'.
*/
function units_measure_save($entity) {
entity_save('units_measure', $entity);
}
/**
* Delete a single entity of type 'units_measure'.
*/
function units_measure_delete($entity) {
entity_delete('units_measure', entity_id('units_measure', $entity));
}
/**
* Delete multiple entities of type 'units_measure'.
*
* @param array $mids
* Array of entity ids to be deleted
*/
function units_measure_delete_multiple($mids) {
entity_delete_multiple('units_measure', $mids);
}
/**
* Retrieve information about a cTools plugin - units operator.
*
* @param string $operator
* Name of a particular units operator plugin to return. If skipped, an array
* of all available units operators plugins will be returned
*
* @return array
* Depending on whether $operator is provided it will be either a single
* array of information about provided $operator units operator plugin or
* array of information on all available units operator plugins
*/
function units_get_operator($operator = NULL) {
ctools_include('plugins');
return ctools_get_plugins('units', 'operator', $operator);
}
/**
* Retrieve information about a cTools plugin - units operator.
*
* The search is conducted by sign symbol of all operators.
*
* @param string $operator
* Sign of a particular units operator plugin to return. If skipped, an array
* of all available units operators will be returned
*
* @return array
* Depending on whether $operator is provided it will be either a single
* array of information about provided $operator units operator plugin or
* array of information aon all available units operator plugins, keyed by
* their signs
*/
function units_get_operator_by_sign($operator = NULL) {
ctools_include('plugins');
$return = array();
foreach (ctools_get_plugins('units', 'operator') as $plugin) {
$return[$plugin['sign']] = $plugin;
}
return isset($operator) ? $return[$operator] : $return;
}
/**
* Check whether the 2 dimensions are equal.
*
* @param array $dimension1
* Dimension array #1
* @param array $dimension2
* Dimension array #2
*
* @return bool
* Whether the 2 provided dimension arrays are equal
*/
function units_dimension_equal($dimension1, $dimension2) {
$diff = units_dimension_subtract($dimension1, $dimension2);
return empty($diff);
}
/**
* Sum 2 dimension arrays.
*
* @param array $dimension1
* Dimension array #1
* @param $dimension2
* Dimension array #2
*
* @return array
* Dimension array: result of the sum of the 2 provided dimension arrays
*/
function units_dimension_add($dimension1, $dimension2) {
units_dimension_normalize($dimension1, $dimension2);
$sum = array();
foreach ($dimension1 as $k => $v) {
$sum[$k] = $dimension1[$k] + $dimension2[$k];
}
return $sum;
}
/**
* Subtract 2 dimension arrays.
*
* @param array $dimension_minuend
* Minuend dimension array
* @param $dimension_subtrahend
* Subtrahend dimension array
*
* @return array
* Difference dimension array
*/
function units_dimension_subtract($dimension_minuend, $dimension_subtrahend) {
units_dimension_normalize($dimension_minuend, $dimension_subtrahend);
$diff = array();
foreach ($dimension_minuend as $k => $v) {
$diff[$k] = $dimension_minuend[$k] - $dimension_subtrahend[$k];
}
return array_filter($diff);
}
/**
* Multiply a dimension array by a constant factor.
*
* @param array $dimension
* Dimension array to be multiplied
* @param int $multiplier
* Constant factor by which to multiply the dimension array
*
* @return array
* Dimension array: result of the multiplication
*/
function units_dimension_multiply($dimension, $multiplier) {
foreach ($dimension as $k => $v) {
$dimension[$k] = $v * $multiplier;
}
return $dimension;
}
/**
* Pre-process 2 dimension arrays.
*
* Make sure both have all known dimensions initialized. Sorting them by the
* dimensions so we can treat them as vectors.
*
* @param array $dimension1
* Dimension array #1
* @param $dimension2
* Dimension array #2
*/
function units_dimension_normalize(&$dimension1, &$dimension2) {
$dimension1 += array_fill_keys(array_keys($dimension2), 0);
$dimension2 += array_fill_keys(array_keys($dimension1), 0);
ksort($dimension1);
ksort($dimension2);
}
/**
* Format one mathematical expression to the format of another.
*
* @param \UnitsMathematicalExpression $input
* Mathematical expression to be formatted
* @param \UnitsMathematicalExpression $output
* Desired format for $input mathematical expression. Constants of this format
* will be filled in based on data stored in $input mathematical expression
*
* @throws UnitsMathematicalExpressionDimensionException
* Exception is thrown if the input and output mathematical expressions are
* of different physical dimension and therefore may not be treated on the
* same scale
*/
function units_mathematical_expression_format_as(UnitsMathematicalExpression $input, UnitsMathematicalExpression &$output) {
if (!units_dimension_equal($input
->dimension(), $output
->dimension())) {
throw new UnitsMathematicalExpressionDimensionException();
}
$quantity = $input
->evaluate();
if (is_null($quantity)) {
// Quantity would be null only in the case if $input mathematical expression
// contains no dimensionless members. It could be the case if $input
// mathematical expression is an atomic unit or such a composed unit that
// decomposes into atomic ones without factors, i.e. the decomposition
// happens only on the physical dimension level. For example, it could be
// square meter (decomposed into "meter * meter") or alike.
// To keep things simple for end users we assume "unit" to actually be
// "1 * unit". That's why we plug in the quantity of 1.
$quantity = 1;
}
$output = $output
->formatQuantity($quantity);
}
/**
* Load mathematical expression from database.
*
* @param int $mathematical_expression_id
* Mathematical expression to load
*
* @return \UnitsMathematicalExpressionWrapper
* Loaded mathematical expression that corresponds to the provided
* $mathematical_expression_id
*
* @throws \UnitsMathematicalExpressionMalformedException
* Exception is thrown if the mathematical expression appears to be poorly
* stored in database (unknown units, operators or anything alike)
*/
function units_mathematical_expression_load($mathematical_expression_id) {
if (!$mathematical_expression_id) {
return NULL;
}
$select = db_select('units_mathematical_expression_postfix', 'e');
$select
->fields('e', array(
'type',
'value_numeric',
'value_string',
'postfix_order',
));
$select
->condition('mathematical_expression_id', $mathematical_expression_id);
$select
->orderBy('postfix_order');
$operators = units_get_operator();
$stack = array();
foreach ($select
->execute() as $token) {
switch ($token->type) {
case UNITS_TOKEN_TYPE_CONSTANT:
$object = new UnitsConstantMathematicalExpression($token->value_string == UNITS_QUANTITY ? $token->value_string : $token->value_numeric);
break;
case UNITS_TOKEN_TYPE_UNIT:
$object = units_unit_machine_name_load($token->value_string);
if (!$object) {
throw new UnitsMathematicalExpressionMalformedException(t('Encountered unknown unit when loading a mathematical expression from database: %unit', array(
'%unit' => $token->value_string,
)));
}
break;
case UNITS_TOKEN_TYPE_OPERATOR:
if (!isset($operators[$token->value_string])) {
throw new UnitsMathematicalExpressionMalformedException(t('Encountered unknown operator when loading a mathematical expression from database: %operator', array(
'%operator' => $token->value_string,
)));
}
$operator2 = units_mathematical_expression_postfix_stack_pop($stack);
$operator1 = units_mathematical_expression_postfix_stack_pop($stack);
$object = units_mathematical_expression_operator_construct($operators[$token->value_string], $operator1, $operator2);
break;
default:
throw new UnitsMathematicalExpressionMalformedException(t('Encountered unknown token type when loading a mathematical expression from database: %token_type', array(
'%token_type' => $token->type,
)));
break;
}
array_push($stack, $object);
}
$object = units_mathematical_expression_postfix_stack_pop($stack);
$wrapper = new UnitsMathematicalExpressionWrapper();
$wrapper
->setExpression($object);
$wrapper
->setMathematicalExpressionId($mathematical_expression_id);
return $wrapper;
}
/**
* Delete a mathematical expression from data.
*
* @param int $mathematical_expression_id
* Mathematical expression ID to be deleted
*/
function units_mathematical_expression_delete($mathematical_expression_id) {
if (intval($mathematical_expression_id) > 0) {
db_delete('units_mathematical_expression_postfix')
->condition('mathematical_expression_id', $mathematical_expression_id)
->execute();
}
}
/**
* Create mathematical expression object from infix notation.
*
* @param string $infix_mathematical_expression
* Mathematical expression written down in infix notation
*
* @return \UnitsMathematicalExpressionWrapper
* In-memory representation of the provided mathematical expression
*
* @throws \UnitsMathematicalExpressionMalformedException
*/
function units_mathematical_expression_create_from_infix($infix_mathematical_expression) {
$infix_mathematical_expression = array_filter(explode(' ', $infix_mathematical_expression), 'units_mathematical_expression_array_filter');
if (empty($infix_mathematical_expression)) {
return NULL;
}
$operators = units_get_operator_by_sign();
// This is an implementation of shunting-yard algorithm.
$stack = array();
$output = array();
foreach ($infix_mathematical_expression as $token) {
if (is_numeric($token) || $token == UNITS_QUANTITY) {
$output[] = $token;
}
elseif ($unit = units_unit_machine_name_load($token)) {
$output[] = $unit
->toPostfix();
}
elseif (isset($operators[$token])) {
while (!empty($stack) && isset($operators[$stack[count($stack) - 1]]) && $operators[$token]['precedence'] <= $operators[$stack[count($stack) - 1]]['precedence']) {
$output[] = array_pop($stack);
}
$stack[] = $token;
}
elseif ($token == '(') {
$stack[] = $token;
}
elseif ($token == ')') {
while (!empty($stack) && end($stack) != '(') {
$output[] = array_pop($stack);
}
// Popping out the opening parenthesis "(" symbol.
array_pop($stack);
}
else {
throw new UnitsMathematicalExpressionMalformedException(t('Unknown token: @token', array(
'@token' => $token,
)));
}
}
while (!empty($stack)) {
$output[] = array_pop($stack);
}
return units_mathematical_expression_create_from_postfix(implode(' ', $output));
}
/**
* Create mathematical expression object from postfix (reverse polish) notation.
*
* @param string $postfix_mathematical_expression
* Mathematical expression written down in postfix (reverse polish) notation
*
* @return \UnitsMathematicalExpressionWrapper
* In-memory representation of the provided mathematical expression
*
* @throws \UnitsMathematicalExpressionMalformedException
*/
function units_mathematical_expression_create_from_postfix($postfix_mathematical_expression) {
$postfix_mathematical_expression_string = $postfix_mathematical_expression;
$postfix_mathematical_expression = array_filter(explode(' ', $postfix_mathematical_expression), 'units_mathematical_expression_array_filter');
if (empty($postfix_mathematical_expression)) {
return NULL;
}
$operators = units_get_operator_by_sign();
$stack = array();
foreach ($postfix_mathematical_expression as $token) {
if (!isset($operators[$token])) {
// This is either a unit or a constant.
if (is_numeric($token) || $token == UNITS_QUANTITY) {
$token = new UnitsConstantMathematicalExpression($token);
}
else {
$unit = units_unit_machine_name_load($token);
if (!$unit) {
throw new UnitsMathematicalExpressionMalformedException(t('Unknown token: @token in expression: @expression', array(
'@token' => $token,
'@expression' => $postfix_mathematical_expression_string,
)));
}
$token = $unit;
}
}
else {
// TODO: shouldn't these stack pops be executed through units_mathematical_expression_postfix_stack_pop()?
$operand2 = array_pop($stack);
$operand1 = array_pop($stack);
$token = units_mathematical_expression_operator_construct($operators[$token], $operand1, $operand2);
}
array_push($stack, $token);
}
// TODO: shouldn't these stack pops be executed through units_mathematical_expression_postfix_stack_pop()?
$result = array_pop($stack);
if (!empty($stack)) {
// The stack must be empty now since we've supposedly processed the whole
// expression. Be it not the case, we may not trust the $result and better
// thrown an exception.
throw new UnitsMathematicalExpressionMalformedException();
}
$wrapper = new UnitsMathematicalExpressionWrapper();
$wrapper
->setExpression($result);
return $wrapper;
}
/**
* Pop an element off a postfix parsing stack.
*
* @param array $stack
* Current stack of postfix parsing
*
* @return UnitsMathematicalExpression|string
* Popped element off the postfix parsing stack. It may be either an instance
* of UnitsMathematicalExpression or a string depending on what stage of
* parsing we are
*
* @throws UnitsMathematicalExpressionMalformedException
* Exception is thrown if the stack is empty
*/
function units_mathematical_expression_postfix_stack_pop(&$stack) {
if (empty($stack)) {
throw new UnitsMathematicalExpressionMalformedException();
}
return array_pop($stack);
}
/**
* Process function for 'units_mathematical_expression' form element.
*/
function units_mathematical_expression_element_process($element) {
$element['#tree'] = TRUE;
$blacklist = array(
'#type',
'#element_validate',
// Make form_builder() regenerate child properties.
'#parents',
'#id',
'#name',
// Do not copy this #process function to prevent form_builder() from
// recursing infinitely.
'#process',
// Ensure proper ordering of children.
'#weight',
// Properties already processed for the parent element.
'#prefix',
'#suffix',
'#attached',
'#processed',
'#theme_wrappers',
// Force form_builder() to load defaults for all children.
'#defaults_loaded',
'#value',
);
// Move this element into sub-element 'infix'.
unset($element['infix']);
foreach (element_properties($element) as $key) {
if (!in_array($key, $blacklist)) {
$element['infix'][$key] = $element[$key];
}
}
$element['infix']['#type'] = 'textfield';
if (isset($element['infix']['#default_value']) && is_object($element['infix']['#default_value'])) {
$element['infix']['#default_value'] = $element['infix']['#default_value']
->getExpression()
->toInfix();
}
return $element;
}
/**
* Validate function for 'units_mathematical_expression' form element.
*/
function units_mathematical_expression_element_validate($element, &$form_state) {
$mathematical_expression = NULL;
try {
$mathematical_expression = units_mathematical_expression_create_from_infix($element['infix']['#value']);
// We evaluate the mathematical expression in order to make sure it contains
// no dimensional contradictions.
if (is_object($mathematical_expression)) {
$mathematical_expression
->getExpression()
->evaluate();
}
} catch (UnitsMathematicalExpressionDimensionException $e) {
form_error($element, t('%title mathematical expression contains error in dimensions.', array(
'%title' => $element['#title'],
)));
} catch (UnitsMathematicalExpressionMalformedException $e) {
form_error($element, t('%title contains malformed mathematical expression. @exception', array(
'%title' => $element['#title'],
'@exception' => $e
->getMessage(),
)));
}
if (is_object($mathematical_expression) && $element['#allowed_dimension'] && !units_dimension_equal($element['#allowed_dimension'], $mathematical_expression
->getExpression()
->dimension())) {
form_error($element, t('%title mismatches allowed dimension.', array(
'%title' => $element['#title'],
)));
}
if (is_object($mathematical_expression)) {
switch ($element['#value_format']) {
case 'object':
break;
case 'postfix':
$mathematical_expression = $mathematical_expression
->getExpression()
->toPostfix();
break;
case 'infix':
$mathematical_expression = $mathematical_expression
->getExpression()
->toInfix();
break;
}
}
form_set_value($element, $mathematical_expression, $form_state);
}
/**
* Initialize DB stored functions necessary for the module.
*
* @param bool $create
* Whether to create (TRUE) or delete (FALSE) the stored functions
*/
function units_mathematical_expression_recreate_stored_functions($create = TRUE) {
// TODO: for now we run on PostgreSQL, so we disable this whole thing.
return;
$operators = units_get_operator_by_sign();
$units_token_type_name = 'units_token_type';
$units_token_type = <<<EOF
CREATE FUNCTION `{<span class="php-variable">$units_token_type_name</span>}` ( token VARCHAR(255) ) RETURNS varchar(255)
BEGIN
IF (token IN (:operator_signs)) THEN
RETURN ('operator');
ELSEIF EXISTS (SELECT 1 FROM {units_unit} WHERE machine_name = token) THEN
RETURN ('unit');
ELSE
RETURN ('constant');
END if;
END
EOF;
units_mathematical_expression_recreate_stored_function($create, $units_token_type_name, $units_token_type, array(
':operator_signs' => array_keys($operators),
));
$units_token_name = 'units_token';
$units_token = <<<EOF
CREATE FUNCTION `{<span class="php-variable">$units_token_name</span>}` ( expression VARCHAR(255), pos INT(10) ) RETURNS VARCHAR(32)
DETERMINISTIC
BEGIN
DECLARE length INT;
DECLARE token_length INT;
DECLARE token VARCHAR(32);
DECLARE delimiter VARCHAR(1);
SET delimiter = ' ';
SET expression = CONCAT(expression, delimiter);
SET length = CHAR_LENGTH(expression) + 1;
SET token_length = LOCATE(delimiter, expression, pos) - pos;
SET token = SUBSTRING(expression, pos, token_length);
RETURN (token);
END
EOF;
units_mathematical_expression_recreate_stored_function($create, $units_token_name, $units_token);
$units_decompose_postfix_name = 'units_decompose_postfix';
$units_decompose_postfix = <<<EOF
CREATE FUNCTION `{<span class="php-variable">$units_decompose_postfix_name</span>}` ( expression VARCHAR(255) ) RETURNS VARCHAR(255)
BEGIN
DECLARE i INT;
DECLARE token VARCHAR(32);
SET i = 1;
WHILE i < CHAR_LENGTH(expression) + 1 DO
SET token = units_token(expression, i);
SET i = i + CHAR_LENGTH(token) + 1;
IF (units_token_type(token) = 'unit') THEN
SELECT REPLACE(expression, machine_name, decomposition_postfix) INTO expression FROM {units_unit} WHERE machine_name = token AND decomposition_postfix <> '' AND decomposition_postfix NOT LIKE :units_quantity;
END IF;
END WHILE;
RETURN (expression);
END
EOF;
$units_decompose_postfix_args = array(
':units_quantity' => '%' . db_like(UNITS_QUANTITY) . '%',
);
units_mathematical_expression_recreate_stored_function($create, $units_decompose_postfix_name, $units_decompose_postfix, $units_decompose_postfix_args);
$units_evaluate_postfix_iteration_name = 'units_evaluate_postfix_iteration';
$units_evaluate_postfix_iteration_args = array();
$units_evaluate_postfix_iteration = array();
foreach ($operators as $operator) {
$sign = $operator['sign'];
$name = $operator['name'];
$sql_evaluate_fragment = $operator['sql evaluate fragment'];
$operand_normalization = array();
for ($i = 1; $i < 3; $i++) {
if ($operator['transparent operand' . $i]) {
$operand_normalization[] = "\n IF (units_token_type(token{$i}) = 'unit') THEN\n SET token{$i} = :{$name}_token{$i};\n END IF;\n ";
$units_evaluate_postfix_iteration_args[$name . '_token' . $i] = $operator['transparent operand' . $i];
}
}
$operand_normalization = implode("\n", $operand_normalization);
$units_evaluate_postfix_iteration[] = <<<EOF
WHEN '{<span class="php-variable">$sign</span>}' THEN
{<span class="php-variable">$operand_normalization</span>}
{<span class="php-variable">$sql_evaluate_fragment</span>}
EOF;
}
$units_evaluate_postfix_iteration = implode("\n", $units_evaluate_postfix_iteration);
$units_evaluate_postfix_iteration = <<<EOF
CREATE FUNCTION `{<span class="php-variable">$units_evaluate_postfix_iteration_name</span>}` ( expression VARCHAR(255) ) RETURNS VARCHAR(255)
BEGIN
DECLARE i INT;
DECLARE token1 VARCHAR(32);
DECLARE token2 VARCHAR(32);
DECLARE token3 VARCHAR(32);
DECLARE token1_length INT;
DECLARE token2_length INT;
DECLARE token3_length INT;
DECLARE result FLOAT;
SET i = 1;
WHILE i < CHAR_LENGTH(expression) + 1 DO
SET token1 = units_token(expression, i);
SET token1_length = CHAR_LENGTH(token1);
SET token2 = units_token(expression, i + token1_length + 1);
SET token2_length = CHAR_LENGTH(token2);
SET token3 = units_token(expression, i + token1_length + 1 + token2_length + 1);
SET token3_length = CHAR_LENGTH(token3);
IF (units_token_type(token1) IN ('constant', 'unit')) AND (units_token_type(token2) IN ('constant', 'unit')) AND (units_token_type(token3) = 'operator') THEN
CASE token3
{<span class="php-variable">$units_evaluate_postfix_iteration</span>}
END CASE;
SET expression = CONCAT(SUBSTRING(expression, 1, i - 1), result, SUBSTRING(expression, i + token1_length + 1 + token2_length + 1 + token3_length + 1 - 1));
ELSE
SET i = i + token1_length + 1;
END IF;
END WHILE;
RETURN (expression);
END
EOF;
units_mathematical_expression_recreate_stored_function($create, $units_evaluate_postfix_iteration_name, $units_evaluate_postfix_iteration, $units_evaluate_postfix_iteration_args);
$units_evaluate_postfix_name = 'units_evaluate_postfix';
$units_evaluate_postfix = <<<EOF
CREATE FUNCTION `{<span class="php-variable">$units_evaluate_postfix_name</span>}` ( expression VARCHAR(255) ) RETURNS FLOAT
BEGIN
DECLARE old_expression VARCHAR(255);
SET expression = units_decompose_postfix(expression);
REPEAT
SET old_expression = expression;
SET expression = units_evaluate_postfix_iteration(expression);
UNTIL old_expression = expression END REPEAT;
RETURN (expression);
END
EOF;
units_mathematical_expression_recreate_stored_function($create, $units_evaluate_postfix_name, $units_evaluate_postfix);
}
/**
* Initialize a particular stored function.
*
* @param bool $create
* Whether to create (TRUE) or to delete (FALSE) the stored function
* @param string $name
* Name of the stored function
* @param string $definition
* SQL definition of the stored function
* @param array $definition_args
* Any parameters that accompany $definition SQL query
*/
function units_mathematical_expression_recreate_stored_function($create, $name, $definition, $definition_args = array()) {
db_query("DROP FUNCTION IF EXISTS `{$name}`");
if ($create) {
db_query($definition, $definition_args);
}
}
/**
* Construct operator object.
*
* @param array $operator
* Definition of cTools "operator" plugin whose object should be constructed
* @param \UnitsMathematicalExpression $operand1
* Operand #1
* @param \UnitsMathematicalExpression $operand2
* Operand #2
*
* @return \UnitsMathematicalOperatorInterface
* Object that represents provided input arguments
*/
function units_mathematical_expression_operator_construct(array $operator, UnitsMathematicalExpression $operand1, UnitsMathematicalExpression $operand2) {
$class = $operator['operator class'];
return new $class($operator, $operand1, $operand2);
}
/**
* Filter infix/postfix annotated mathematical expressions.
*
* @param string $token
* Token to filter
*
* @return bool
* Whether the token is accepted or should be disregarded
*/
function units_mathematical_expression_array_filter($token) {
return $token !== '';
}
/**
* Interface of "mathematical expression".
*/
interface UnitsMathematicalExpression {
/**
* Determine physical dimension of this mathematical expression.
*
* @return array
* Dimension array of this mathematical expression
*/
public function dimension();
/**
* Test whether this mathematical expression includes a dimensionless member.
*
* Whether this mathematical expression contains at least 1 dimensionless
* member.
*
* @return bool
* Whether this mathematical expression contains at least 1 dimensionless
* member
*/
public function containsDimensionlessMember();
/**
* Format a certain amount of quantity within this mathematical expression.
*
* @param float $quantity
* Quantity to be formatted
*
* @return UnitsMathematicalExpression
* Formatted quantity into this mathematical expression. Sometimes the
* mathematical expression itself must mutate in order to format the
* quantity. So the returned mathematical expression may not necessarily be
* the mathematical expression on which this method was invoked. For
* example, the expression "unit" would mutate into "1 * unit" in order to
* have a dimensionless member and therefore be able to format the $quantity
*/
public function formatQuantity($quantity);
/**
* Numerically evaluate this mathematical expression.
*
* @return float
* Numerical value of this mathematical expression
*/
public function evaluate();
/**
* Decompose (simplify) this mathematical expression.
*
* @return UnitsMathematicalExpression
* Decomposed (simplified) version of this mathematical expression
*/
public function decompose();
/**
* Represent this mathematical expression in postfix notation.
*
* Represent this mathematical expression in postfix (reverse polish)
* notation.
*
* @param array $options
* Options regarding how to format this mathematical expression
* TODO: document possible options
*
* @return string
* Postfix (reverse polish) notation representation of this mathematical
* expression
*/
public function toPostfix($options = array());
/**
* Represent this mathematical expression in human-friendly infix notation.
*
* @param array $options
* Options regarding how to format this mathematical expression
* TODO: document possible options
*
* @return string
* Human-friendly formatted version of this mathematical expression taking
* into account provided $options
*/
public function toInfix($options = array());
/**
* Whether this expression is linearly decomposable.
*
* @return bool
* Whether this expression is linearly decomposable, i.e. its decomposition
* can be plugged in into another mathematical expression via multiplication
* without losing mathematical sense
*/
public function isLinear();
/**
* Save the mathematical expression into database.
*
* @param int $mathematical_expression_id
* If the ID of the mathematical expression is known, under which it should
* be saved, provide it here. Otherwise it will be generated automatically
* @param int $order
* Order of this member when the mathematical expression is written down in
* postfix notation. Primarily this argument is used for internal purposes
*
* @return int
* Mathematical expression ID under which this expression has been saved in
* the database
*/
public function unitsMathematicalExpressionSave($mathematical_expression_id, &$order);
}
/**
* Interface of mathematical operator.
*/
interface UnitsMathematicalOperatorInterface extends UnitsMathematicalExpression {
/**
* Retrieve operand #1 from this mathematical operator.
*
* @return UnitsMathematicalExpression
* Operand #1 from this mathematical expression
*/
public function operand1();
/**
* Retrieve operand #2 from this mathematical operator.
*
* @return UnitsConstantMathematicalExpression
* Operand #2 from this mathematical expression
*/
public function operand2();
/**
* Numerically isolate a certain operand of this mathematical expression.
*
* Given result of this mathematical expression, numerically, i.e.
* dimensionlessly isolate a certain operand of this mathematical expression.
*
* @param \UnitsMathematicalExpression $equal_expression
* Result of this mathematical expression
* @param int $operand_to_isolate
* What operand of this mathematical expression to isolate. Allowed values
* are:
* - 1: to isolate operand #1
* - 2: to isolate operand #2
*
* @return UnitsMathematicalExpression
* Mathematical expression to which the isolated member numerically equals
*/
public function isolateOperand(UnitsMathematicalExpression $equal_expression, $operand_to_isolate);
}
/**
* Abstract implementation of "mathematical operator" interface.
*/
abstract class AbstractUnitsMathematicalOperator implements UnitsMathematicalOperatorInterface {
/**
* Definition of the underlying cTools 'operator' plugin.
*
* @var array
*/
protected $operator;
/**
* Operand #1 of this mathematical expression.
*
* @var UnitsMathematicalExpression
*/
protected $operand1;
/**
* Operand #2 of this mathematical expression.
*
* @var UnitsMathematicalExpression
*/
protected $operand2;
/**
* UnitsOperatorMathematicalExpression constructor.
*
* @param array $operator
* Definition of a cTools 'operator' plugin that represents the specific
* operator
* @param \UnitsMathematicalExpression $operand1
* Operand #1 for this mathematical expression
* @param \UnitsMathematicalExpression $operand2
* Operand #2 for this mathematical expression
*/
public function __construct($operator, UnitsMathematicalExpression $operand1, UnitsMathematicalExpression $operand2) {
$this->operator = $operator;
$this->operand1 = $operand1;
$this->operand2 = $operand2;
}
/**
* Determine physical dimension of this mathematical expression.
*
* @return array
* Dimension array of this mathematical expression
*/
public function dimension() {
$dimension_callback = ctools_plugin_get_function($this->operator, 'dimension callback');
list($evaluate1, $evaluate2) = $this
->evaluateOperands();
return $dimension_callback($this->operand1
->dimension(), $this->operand2
->dimension(), $evaluate1, $evaluate2);
}
/**
* Test whether this mathematical expression includes a dimensionless member.
*
* Whether this mathematical expression contains at least 1 dimensionless
* member.
*
* @return bool
* Whether this mathematical expression contains at least 1 dimensionless
* member
*/
public function containsDimensionlessMember() {
return $this->operand1
->containsDimensionlessMember() || $this->operand2
->containsDimensionlessMember();
}
/**
* Format a certain amount of quantity within this mathematical expression.
*
* @param float $quantity
* Quantity to be formatted
*
* @return UnitsMathematicalExpression
* Formatted quantity into this mathematical expression. Sometimes the
* mathematical expression itself must mutate in order to format the
* quantity. So the returned mathematical expression may not necessarily be
* the mathematical expression on which this method was invoked. For
* example, the expression "unit" would mutate into "1 * unit" in order to
* have a dimensionless member and therefore be able to format the $quantity
*/
public function formatQuantity($quantity) {
$contains_dimensionless1 = $this->operand1
->containsDimensionlessMember();
$contains_dimensionless2 = $this->operand2
->containsDimensionlessMember();
list($quantity1, $quantity2) = $this
->evaluateOperands();
if ($contains_dimensionless1 xor $contains_dimensionless2) {
if ($contains_dimensionless1) {
$this->operand1
->formatQuantity($quantity / $quantity2);
}
else {
$this->operand2
->formatQuantity($quantity / $quantity1);
}
}
else {
$split_quantity = ctools_plugin_get_function($this->operator, 'split quantity callback');
list($quantity1, $quantity2) = $split_quantity($quantity, $quantity1, $quantity2, $this->operator);
$this->operand1
->formatQuantity($quantity1);
$this->operand2
->formatQuantity($quantity2);
}
return $this;
}
/**
* Numerically evaluate this mathematical expression.
*
* @return float
* Numerical value of this mathematical expression
*
* @throws UnitsMathematicalExpressionDimensionException
* Exception is thrown if this mathematical expression has inconsistency in
* physical dimensions
*/
public function evaluate() {
if ($this->operator['dimension check'] && !units_dimension_equal($this->operand1
->dimension(), $this->operand2
->dimension())) {
throw new UnitsMathematicalExpressionDimensionException();
}
list($evaluate1, $evaluate2) = $this
->evaluateOperands();
$evaluate_callback = ctools_plugin_get_function($this->operator, 'evaluate callback');
return $evaluate_callback($evaluate1, $evaluate2);
}
/**
* Decompose (simplify) this mathematical expression.
*
* @return UnitsMathematicalExpression
* Decomposed (simplified) version of this mathematical expression
*/
public function decompose() {
return units_mathematical_expression_operator_construct($this->operator, $this
->operand1()
->decompose(), $this
->operand2()
->decompose());
}
/**
* Represent this mathematical expression in postfix notation.
*
* Represent this mathematical expression in postfix (reverse polish)
* notation.
*
* @param array $options
* Options regarding how to format this mathematical expression
*
* @return string
* Postfix (reverse polish) notation representation of this mathematical
* expression
*/
public function toPostfix($options = array()) {
return implode(' ', array(
$this->operand1
->toPostfix(),
$this->operand2
->toPostfix(),
$this->operator['sign'],
));
}
/**
* Represent this mathematical expression in human-friendly infix notation.
*
* @param array $options
* Options regarding how to format this mathematical expression
*
* @return string
* Human-friendly formatted version of this mathematical expression taking
* into account provided $options
*/
public function toInfix($options = array()) {
$child_options = array(
'parent precedence' => $this->operator['precedence'],
) + $options;
$output = implode(' ', array(
$this->operand1
->toInfix(array(
'operand' => 1,
) + $child_options),
$this->operator['sign'],
$this->operand2
->toInfix(array(
'operand' => 2,
) + $child_options),
));
if (isset($options['parent precedence']) && ($options['parent precedence'] > $this->operator['precedence'] || $options['operand'] == 2 && $options['parent precedence'] == $this->operator['precedence'])) {
$output = '( ' . $output . ' )';
}
return $output;
}
/**
* Whether this expression is linearly decomposable.
*
* @return bool
* Whether this expression is linearly decomposable, i.e. its decomposition
* can be plugged in into another mathematical expression via multiplication
* without losing sense
*/
public function isLinear() {
return TRUE;
}
/**
* Save the mathematical expression into database.
*
* @param int $mathematical_expression_id
* If the ID of the mathematical expression is known, under which it should
* be saved, provide it here. Otherwise it will be generated automatically
* @param int $order
* Order of this member when the mathematical expression is written down in
* postfix notation. Primarily this argument is used for internal purposes
*
* @return int
* Mathematical expression ID under which this expression has been saved in
* the database
*/
public function unitsMathematicalExpressionSave($mathematical_expression_id, &$order) {
$mathematical_expression_id = $this
->operand1()
->unitsMathematicalExpressionSave($mathematical_expression_id, $order);
$this
->operand2()
->unitsMathematicalExpressionSave($mathematical_expression_id, $order);
db_insert('units_mathematical_expression_postfix')
->fields(array(
'mathematical_expression_id' => $mathematical_expression_id,
'type' => UNITS_TOKEN_TYPE_OPERATOR,
'value_string' => $this->operator['name'],
'postfix_order' => ++$order,
))
->execute();
return $mathematical_expression_id;
}
/**
* Retrieve operand #1 from this mathematical operator.
*
* @return UnitsMathematicalExpression
* Operand #1 from this mathematical expression
*/
public function operand1() {
return $this->operand1;
}
/**
* Retrieve operand #2 from this mathematical operator.
*
* @return UnitsConstantMathematicalExpression
* Operand #2 from this mathematical expression
*/
public function operand2() {
return $this->operand2;
}
/**
* Numerically isolate a certain operand of this mathematical expression.
*
* Given result of this mathematical expression, numerically, i.e.
* dimensionlessly isolate a certain operand of this mathematical expression.
*
* @param \UnitsMathematicalExpression $equal_expression
* Result of this mathematical expression
* @param int $operand_to_isolate
* What operand of this mathematical expression to isolate. Allowed values
* are:
* - 1: to isolate operand #1
* - 2: to isolate operand #2
*
* @return UnitsConstantMathematicalExpression
* Mathematical expression to which the isolated member numerically equals
*/
public function isolateOperand(UnitsMathematicalExpression $equal_expression, $operand_to_isolate) {
$isolate_callback = ctools_plugin_get_function($this->operator, 'isolate operand callback');
if ($isolate_callback) {
list($operand1, $operand2) = $this
->evaluateOperands();
if ($operand_to_isolate == 1) {
$operand1 = $this
->operand1();
$operand2 = new UnitsConstantMathematicalExpression($operand2);
}
else {
$operand1 = new UnitsConstantMathematicalExpression($operand1);
$operand2 = $this
->operand2();
}
return $isolate_callback($operand1, $operand2, $equal_expression, $operand_to_isolate);
}
}
/**
* Numerically evaluate both operands and return them as an array.
*
* @return array
* Array of length 2: the 2 operands numerically evaluated
*/
protected function evaluateOperands() {
$evaluate1 = $this->operand1
->evaluate();
$evaluate2 = $this->operand2
->evaluate();
if (is_null($evaluate1)) {
$evaluate1 = $this->operator['transparent operand1'];
}
if (is_null($evaluate2)) {
$evaluate2 = $this->operator['transparent operand2'];
}
return array(
$evaluate1,
$evaluate2,
);
}
}
/**
* Implementation of "mathematical operator" interface for a linear operation.
*
* Implementation of "mathematical operator" interface for a linear mathematical
* operation.
*/
class UnitsMathematicalOperatorLinear extends AbstractUnitsMathematicalOperator {
}
/**
* Implementation of "mathematical operator" interface for non-linear operation.
*
* Implementation of "mathematical operator" interface for a non-linear
* mathematical operation.
*/
class UnitsMathematicalOperatorNonLinear extends AbstractUnitsMathematicalOperator {
/**
* Constructor.
*
* @param array $operator
* Definition of a cTools 'operator' plugin that represents the specific
* operator
* @param \UnitsConstantMathematicalExpression $operand1
* Operand #1 for this mathematical expression
* @param \UnitsEntity $operand2
* Operand #2 for this mathematical expression
*
* @throws UnitsMathematicalExpressionMalformedException
* Exception is thrown if the linearity is not consistent (none or both
* non-linear operands)
*/
public function __construct(array $operator, \UnitsConstantMathematicalExpression $operand1, \UnitsEntity $operand2) {
parent::__construct($operator, $operand1, $operand2);
if ($this
->nonLinearMember()
->isLinear()) {
throw new UnitsMathematicalExpressionMalformedException(t('Expression %expression operates on 2 linear members while the operator is non-linear.', array(
'%expression' => $this
->toInfix(),
)));
}
}
/**
* Determine physical dimension of this mathematical expression.
*
* @return array
* Dimension array of this mathematical expression
*/
public function dimension() {
return $this
->nonLinearMember()
->dimension();
}
/**
* Format a certain amount of quantity within this mathematical expression.
*
* @param float $quantity
* Quantity to be formatted
*
* @return UnitsMathematicalExpression
* Formatted quantity into this mathematical expression. Sometimes the
* mathematical expression itself must mutate in order to format the
* quantity. So the returned mathematical expression may not necessarily be
* the mathematical expression on which this method was invoked. For
* example, the expression "unit" would mutate into "1 * unit" in order to
* have a dimensionless member and therefore be able to format the $quantity
*/
public function formatQuantity($quantity) {
$unit = $this
->nonLinearMember();
$decomposition = $unit
->inverseDecompose();
// Swap the quantity placeholder with the real value.
$decomposition = units_mathematical_expression_create_from_postfix(str_replace(UNITS_QUANTITY, $quantity, $decomposition
->toPostfix()))
->getExpression();
$this
->linearMember()
->formatQuantity($decomposition
->evaluate());
return $this;
}
/**
* Numerically evaluate this mathematical expression.
*
* @return float
* Numerical value of this mathematical expression
*/
public function evaluate() {
return $this
->decompose()
->evaluate();
}
/**
* Decompose (simplify) this mathematical expression.
*
* @return UnitsMathematicalExpression
* Decomposed (simplified) version of this mathematical expression
*/
public function decompose() {
$unit = $this
->nonLinearMember();
// This is somewhat hacky, but it's the best I could come up with. We will
// temporarily swap decomposition expression in the unit with the specific
// quantity and then will invoke the decomposition on the unit.
$temporary_decomposition = $unit->decomposition;
$unit->decomposition = units_mathematical_expression_create_from_postfix(str_replace(UNITS_QUANTITY, $this
->linearMember()
->toPostfix(), $unit->decomposition
->getExpression()
->toPostfix()));
$decomposition = $unit
->decompose();
$unit->decomposition = $temporary_decomposition;
return $decomposition;
}
/**
* Whether this expression is linearly decomposable.
*
* @return bool
* Whether this expression is linearly decomposable, i.e. its decomposition
* can be plugged in into another mathematical expression via multiplication
* without losing sense
*/
public function isLinear() {
return FALSE;
}
/**
* Fetch the linear member of the non-linear operator.
*
* @return \UnitsConstantMathematicalExpression
* The linear member of the non-linear operator, which is always a constant
*/
public function linearMember() {
return $this->operand1;
}
/**
* Fetch the non-linear member of the non-linear operator.
*
* @return \UnitsEntity
* The non-linear member of the non-linear operator, which is always a
* non-linear unit entity
*/
public function nonLinearMember() {
return $this->operand2;
}
}
/**
* Implementation of "mathematical expression" interface for a constant.
*
* Implementation of "mathematical expression" interface for a dimensionless
* constant.
*/
class UnitsConstantMathematicalExpression implements UnitsMathematicalExpression {
/**
* Constant that is held in this mathematical expression.
*
* @var float
*/
protected $constant;
/**
* UnitsConstantMathematicalExpression constructor.
*
* @param int|float $constant
* Constant that should be held in this mathematical expression
*/
public function __construct($constant) {
$this->constant = $constant;
}
/**
* Determine physical dimension of this mathematical expression.
*
* @return array
* Dimension array of this mathematical expression
*/
public function dimension() {
return array();
}
/**
* Test whether this mathematical expression includes a dimensionless member.
*
* Whether this mathematical expression contains at least 1 dimensionless
* member.
*
* @return bool
* Whether this mathematical expression contains at least 1 dimensionless
* member
*/
public function containsDimensionlessMember() {
return TRUE;
}
/**
* Format a certain amount of quantity within this mathematical expression.
*
* @param float $quantity
* Quantity to be formatted
*
* @return UnitsMathematicalExpression
* Formatted quantity into this mathematical expression. Sometimes the
* mathematical expression itself must mutate in order to format the
* quantity. So the returned mathematical expression may not necessarily be
* the mathematical expression on which this method was invoked. For
* example, the expression "unit" would mutate into "1 * unit" in order to
* have a dimensionless member and therefore be able to format the $quantity
*/
public function formatQuantity($quantity) {
$this->constant = $quantity;
return $this;
}
/**
* Numerically evaluate this mathematical expression.
*
* @return float
* Numerical value of this mathematical expression
*/
public function evaluate() {
return $this->constant;
}
/**
* Decompose (simplify) this mathematical expression.
*
* @return UnitsMathematicalExpression
* Decomposed (simplified) version of this mathematical expression
*/
public function decompose() {
return $this;
}
/**
* Represent this mathematical expression in postfix notation.
*
* Represent this mathematical expression in postfix (reverse polish)
* notation.
*
* @param array $options
* Options regarding how to format this mathematical expression
*
* @return string
* Postfix (reverse polish) notation representation of this mathematical
* expression
*/
public function toPostfix($options = array()) {
return $this->constant;
}
/**
* Represent this mathematical expression in human-friendly infix notation.
*
* @param array $options
* Options regarding how to format this mathematical expression
*
* @return string
* Human-friendly formatted version of this mathematical expression taking
* into account provided $options
*/
public function toInfix($options = array()) {
return $this->constant;
}
/**
* Whether this expression is linearly decomposable.
*
* @return bool
* Whether this expression is linearly decomposable, i.e. its decomposition
* can be plugged in into another mathematical expression via multiplication
* without losing sense
*/
public function isLinear() {
return TRUE;
}
/**
* Save the mathematical expression into database.
*
* @param int $mathematical_expression_id
* If the ID of the mathematical expression is known, under which it should
* be saved, provide it here. Otherwise it will be generated automatically
* @param int $order
* Order of this member when the mathematical expression is written down in
* postfix notation. Primarily this argument is used for internal purposes
*
* @return int
* Mathematical expression ID under which this expression has been save in
* the database
*/
public function unitsMathematicalExpressionSave($mathematical_expression_id, &$order) {
if (!$mathematical_expression_id) {
// TODO: this should be possible to do as: INSERT INTO ... FROM SELECT ... thereby making unnecessary the transaction.
// See https://www.drupal.org/node/310079 for more details.
$transaction = db_transaction();
$select = db_select('units_mathematical_expression_postfix', 'e');
$select
->addExpression('MAX(e.mathematical_expression_id) + 1', 'mathematical_expression_id');
$mathematical_expression_id = $select
->execute()
->fetchField();
if (!$mathematical_expression_id) {
$mathematical_expression_id = 1;
}
}
db_insert('units_mathematical_expression_postfix')
->fields(array(
'type' => UNITS_TOKEN_TYPE_CONSTANT,
'mathematical_expression_id' => $mathematical_expression_id,
$this->constant == UNITS_QUANTITY ? 'value_string' : 'value_numeric' => $this->constant,
'postfix_order' => ++$order,
))
->execute();
return $mathematical_expression_id;
}
}
/**
* Exception thrown whenever a units mathematical expression is malformed.
*/
class UnitsMathematicalExpressionMalformedException extends Exception {
}
/**
* Exception thrown whenever there is a physical dimension inconsistency.
*
* Exception thrown whenever a units mathematical expression contains physical
* dimension inconsistency.
*/
class UnitsMathematicalExpressionDimensionException extends UnitsMathematicalExpressionMalformedException {
}
/**
* Wrapper around "mathematical expression" objects for ease of db storage.
*/
class UnitsMathematicalExpressionWrapper {
/**
* Mathematical expression ID of this mathematical expression, if known.
*
* @var int
*/
protected $mathematical_expression_id;
/**
* Pointer to the underlying mathematical expression object.
*
* @var UnitsMathematicalExpression
*/
protected $expression;
/**
* Get underlying mathematical expression.
*
* @return \UnitsMathematicalExpression
* The underlying mathematical expression
*/
public function getExpression() {
return $this->expression;
}
/**
* Set underlying mathematical expression.
*
* @param \UnitsMathematicalExpression $expression
* Mathematical expression to set as underlying one
*/
public function setExpression(UnitsMathematicalExpression $expression) {
$this->expression = $expression;
}
/**
* Save the underlying mathematical expression into database.
*/
public function save() {
if ($this
->getMathematicalExpressionId()) {
units_mathematical_expression_delete($this
->getMathematicalExpressionId());
}
$order = 0;
$this
->setMathematicalExpressionId($this
->getExpression()
->unitsMathematicalExpressionSave($this
->getMathematicalExpressionId(), $order));
}
/**
* Get mathematical expression ID of this mathematical expression.
*
* @return int
* Mathematical expression ID of this mathematical expression
*/
public function getMathematicalExpressionId() {
return $this->mathematical_expression_id;
}
/**
* Set mathematical expression ID of this mathematical expression.
*
* When saving this mathematical expression into database the old entries of
* this mathematical expression ID will be removed prior saving the in-memory
* representation.
*
* @param int $mathematical_expression_id
* Mathematical expression ID of this mathematical expression
*/
public function setMathematicalExpressionId($mathematical_expression_id) {
$this->mathematical_expression_id = $mathematical_expression_id;
}
}
Functions
Name | Description |
---|---|
units_ctools_plugin_directory | Implements hook_ctools_plugin_directory(). |
units_ctools_plugin_type | Implements hook_ctools_plugin_type(). |
units_dimension_add | Sum 2 dimension arrays. |
units_dimension_equal | Check whether the 2 dimensions are equal. |
units_dimension_multiply | Multiply a dimension array by a constant factor. |
units_dimension_normalize | Pre-process 2 dimension arrays. |
units_dimension_subtract | Subtract 2 dimension arrays. |
units_element_info | Implements hook_element_info(). |
units_entity_access | Access callback for entity types 'units_measure' and 'units_unit'. |
units_entity_info | Implements hook_entity_info(). |
units_get_operator | Retrieve information about a cTools plugin - units operator. |
units_get_operator_by_sign | Retrieve information about a cTools plugin - units operator. |
units_mathematical_expression_array_filter | Filter infix/postfix annotated mathematical expressions. |
units_mathematical_expression_create_from_infix | Create mathematical expression object from infix notation. |
units_mathematical_expression_create_from_postfix | Create mathematical expression object from postfix (reverse polish) notation. |
units_mathematical_expression_delete | Delete a mathematical expression from data. |
units_mathematical_expression_element_process | Process function for 'units_mathematical_expression' form element. |
units_mathematical_expression_element_validate | Validate function for 'units_mathematical_expression' form element. |
units_mathematical_expression_format_as | Format one mathematical expression to the format of another. |
units_mathematical_expression_load | Load mathematical expression from database. |
units_mathematical_expression_operator_construct | Construct operator object. |
units_mathematical_expression_postfix_stack_pop | Pop an element off a postfix parsing stack. |
units_mathematical_expression_recreate_stored_function | Initialize a particular stored function. |
units_mathematical_expression_recreate_stored_functions | Initialize DB stored functions necessary for the module. |
units_measure_delete | Delete a single entity of type 'units_measure'. |
units_measure_delete_multiple | Delete multiple entities of type 'units_measure'. |
units_measure_load | Load an entity of entity type 'units_measure' by its ID. |
units_measure_load_multiple | Load multiple entities of entity type 'units_unit'. |
units_measure_machine_name_load | Load a single entity of type 'units_measure' loading by its machine name. |
units_measure_save | Save an entity of type 'units_measure'. |
units_units_measure_delete | Implements hook_ENTITY_TYPE_delete(). |
units_units_measure_update | Implements hook_ENTITY_TYPE_update(). |
units_units_unit_delete | Implements hook_ENTITY_TYPE_delete(). |
units_units_unit_load | Implements hook_ENTITY_TYPE_load(). |
units_units_unit_presave | Implements hook_ENTITY_TYPE_presave(). |
units_unit_by_measure_load_multiple | Load all units of the supplied measure. |
units_unit_delete | Delete a single entity of type 'units_unit'. |
units_unit_delete_multiple | Delete multiple entities of type 'units_unit'. |
units_unit_load | Load an entity of entity type 'units_unit' by its ID. |
units_unit_load_multiple | Load multiple entities of entity type 'units_unit'. |
units_unit_machine_name_load | Load a single entity of type 'units_unit' loading by its machine name. |
units_unit_save | Save an entity of type 'units_unit'. |
Constants
Name | Description |
---|---|
UNITS_QUANTITY | Denote placeholder for quantity to use in decomposition of non-linear units. |
UNITS_TOKEN_TYPE_CONSTANT | Denote constant token type. |
UNITS_TOKEN_TYPE_OPERATOR | Denote operator token type. |
UNITS_TOKEN_TYPE_UNIT | Denote unit token type. |
Classes
Name | Description |
---|---|
AbstractUnitsMathematicalOperator | Abstract implementation of "mathematical operator" interface. |
UnitsConstantMathematicalExpression | Implementation of "mathematical expression" interface for a constant. |
UnitsMathematicalExpressionDimensionException | Exception thrown whenever there is a physical dimension inconsistency. |
UnitsMathematicalExpressionMalformedException | Exception thrown whenever a units mathematical expression is malformed. |
UnitsMathematicalExpressionWrapper | Wrapper around "mathematical expression" objects for ease of db storage. |
UnitsMathematicalOperatorLinear | Implementation of "mathematical operator" interface for a linear operation. |
UnitsMathematicalOperatorNonLinear | Implementation of "mathematical operator" interface for non-linear operation. |
Interfaces
Name | Description |
---|---|
UnitsMathematicalExpression | Interface of "mathematical expression". |
UnitsMathematicalOperatorInterface | Interface of mathematical operator. |