...
 
Commits (5)
......@@ -239,10 +239,22 @@ By default, SI and IEC prefixes are supported.
Not just plain values but whole expressions are supported.
a a = 5
b b = 10
a a=5 = 5
b b=10 = 10
c c=a*sqrt(b) meter to inch = 622.4956...
Also combined units are being supported.
#1 5km/h to in/sec = 10.9361...
#2 12 l/sec/m^2 in l/hour/sqin = 27.8709...
There is also an automatic conversion from a unit with a prefix to a unit
without one, if no target unit is specified.
#1 1km = 1000
#2 1km/h = 1000
#### Custom units files
Additional units can be specified in a file and passed to jMathPaper with the
......
......@@ -37,8 +37,9 @@ import org.bonsaimind.jmathpaper.core.evaluatedexpressions.BooleanEvaluatedExpre
import org.bonsaimind.jmathpaper.core.evaluatedexpressions.FunctionEvaluatedExpression;
import org.bonsaimind.jmathpaper.core.evaluatedexpressions.NumberEvaluatedExpression;
import org.bonsaimind.jmathpaper.core.resources.ResourceLoader;
import org.bonsaimind.jmathpaper.core.units.Prefix;
import org.bonsaimind.jmathpaper.core.units.PrefixedUnit;
import org.bonsaimind.jmathpaper.core.units.CompoundUnit;
import org.bonsaimind.jmathpaper.core.units.CompoundUnit.Token;
import org.bonsaimind.jmathpaper.core.units.CompoundUnit.TokenType;
import org.bonsaimind.jmathpaper.core.units.UnitConverter;
import com.udojava.evalex.Expression;
......@@ -223,8 +224,8 @@ public class Evaluator {
processedExpression = idMatcher.group("EXPRESSION");
}
PrefixedUnit unitFrom = null;
PrefixedUnit unitTo = null;
CompoundUnit unitFrom = null;
CompoundUnit unitTo = null;
Matcher expressionUnitSeparatorMatcher = EXPRESSION_UNIT_SEPARATOR.matcher(processedExpression);
......@@ -235,12 +236,12 @@ public class Evaluator {
String[] unitParts = splitUnitConversion(unitsPart);
if (unitParts[0] != null && unitParts[0].isEmpty()) {
unitFrom = unitConverter.getPrefixedUnit(expressionPart.trim());
unitFrom = unitConverter.getCompoundUnit(expressionPart.trim());
if (unitFrom != null) {
expressionPart = "1";
} else {
unitFrom = unitConverter.getPrefixedUnit(unitParts[1]);
unitFrom = unitConverter.getCompoundUnit(unitParts[1]);
if (unitFrom == null) {
throw new InvalidExpressionException("No such unit: " + unitParts[1]);
......@@ -250,7 +251,7 @@ public class Evaluator {
unitParts[1] = null;
}
} else if (unitParts[1] != null && unitParts[1].isEmpty()) {
unitFrom = unitConverter.getPrefixedUnit(expressionPart.trim());
unitFrom = unitConverter.getCompoundUnit(expressionPart.trim());
if (unitFrom == null) {
throw new InvalidExpressionException("No such unit: " + expressionPart.trim());
......@@ -259,7 +260,7 @@ public class Evaluator {
expressionPart = "1";
unitParts[1] = unitParts[0];
} else {
unitFrom = unitConverter.getPrefixedUnit(unitParts[0]);
unitFrom = unitConverter.getCompoundUnit(unitParts[0]);
if (unitFrom == null) {
throw new InvalidExpressionException("No such unit: " + unitParts[0]);
......@@ -267,9 +268,9 @@ public class Evaluator {
}
if (unitParts[1] == null) {
unitTo = new PrefixedUnit(Prefix.BASE, unitFrom.getUnit());
unitTo = unitFrom.atBase();
} else {
unitTo = unitConverter.getPrefixedUnit(unitParts[1]);
unitTo = unitConverter.getCompoundUnit(unitParts[1]);
if (unitTo == null) {
throw new InvalidExpressionException("No such unit: " + unitParts[1]);
......@@ -278,11 +279,15 @@ public class Evaluator {
processedExpression = expressionPart;
} else if (!isKnown(processedExpression)) {
unitFrom = unitConverter.getPrefixedUnit(processedExpression);
unitFrom = unitConverter.getCompoundUnit(processedExpression);
if (unitFrom != null) {
unitTo = new PrefixedUnit(Prefix.BASE, unitFrom.getUnit());
processedExpression = "1";
if (!isKnown(unitFrom)) {
unitTo = unitFrom.atBase();
processedExpression = "1";
} else {
unitFrom = null;
}
}
}
......@@ -346,6 +351,18 @@ public class Evaluator {
return "#" + Integer.toString(expressionCounter);
}
private boolean isKnown(CompoundUnit compoundUnit) {
for (Token token : compoundUnit.getTokens()) {
if (token.getTokenType() == TokenType.UNIT) {
if (!isKnown(token.getValue())) {
return false;
}
}
}
return true;
}
private boolean isKnown(String name) {
for (EvaluatedExpression evaluatedExpression : previousEvaluatedExpressions) {
if (evaluatedExpression.getId().equals(name)) {
......@@ -398,7 +415,7 @@ public class Evaluator {
};
}
splitIndex = unitConversionString.indexOf(" ");
splitIndex = unitConversionString.lastIndexOf(" ");
if (splitIndex >= 0) {
return new String[] {
......
......@@ -44,6 +44,30 @@ public class CompoundUnit {
this.tokens.addAll(tokens);
}
/**
* Returns a {@link CompoundUnit} derived from this one with all units at
* their base, meaning with the {@link Prefix#BASE}.
*
* @return A {@link CompoundUnit} derived from this one wtih all units at
* their base.
*/
public CompoundUnit atBase() {
List<Token> tokensAtBase = new ArrayList<>();
for (Token token : tokens) {
if (token.getTokenType() == TokenType.UNIT) {
tokensAtBase.add(new Token(
token.getValue(),
token.getTokenType(),
token.getUnit().atBase()));
} else {
tokensAtBase.add(token);
}
}
return new CompoundUnit(tokensAtBase);
}
/**
* Gets the {@link List} of {@link Token}s which makes up this
* {@link CompoundUnit}.
......
......@@ -56,6 +56,17 @@ public class PrefixedUnit {
this.unit = unit;
}
/**
* A {@link PrefixedUnit} derived from this one with the {@link Prefix#BASE}
* .
*
* @return A {@link PrefixedUnit} derived from this one with the
* {@link Prefix#BASE}.
*/
public PrefixedUnit atBase() {
return new PrefixedUnit(Prefix.BASE, unit);
}
/**
* {@inheritDoc}
*/
......
......@@ -149,10 +149,16 @@ public class UnitConverter {
currentTokenType = TokenType.UNIT;
} else if (isOperatorPart(character)) {
if (currentTokenType != TokenType.OPERATOR && currentToken.length() > 0) {
PrefixedUnit prefixedUnit = getPrefixedUnit(currentToken.toString());
if (prefixedUnit == null) {
return null;
}
tokens.add(new Token(
currentToken.toString(),
currentTokenType,
getPrefixedUnit(currentToken.toString())));
prefixedUnit));
currentToken.delete(0, currentToken.length());
}
......@@ -169,10 +175,16 @@ public class UnitConverter {
currentTokenType,
null));
} else {
PrefixedUnit prefixedUnit = getPrefixedUnit(currentToken.toString());
if (prefixedUnit == null) {
return null;
}
tokens.add(new Token(
currentToken.toString(),
currentTokenType,
getPrefixedUnit(currentToken.toString())));
prefixedUnit));
}
}
......
......@@ -88,6 +88,33 @@ public class TestAbstractPapersUi extends AbstractPapersUi {
Assert.assertTrue(quitCalled);
}
@Test
public void testCompoundUnitConversions() throws CommandExecutionException, InvalidExpressionException {
// Load the defaults
ResourceLoader.processResource("units/iec.prefixes", getPaper().getEvaluator().getUnitConverter()::loadPrefix);
ResourceLoader.processResource("units/si.prefixes", getPaper().getEvaluator().getUnitConverter()::loadPrefix);
ResourceLoader.processResource("units/default.units", getPaper().getEvaluator().getUnitConverter()::loadUnit);
ResourceLoader.processResource("units/default.conversions", getPaper().getEvaluator().getUnitConverter()::loadConversion);
process("1m/sec to km/h");
assertLastResult("3.6");
process("1l/sec/m^2 to usfloz/hour/in^2");
assertLastResult("78.535637590755700991921464362409");
process("1 km/h m/h");
assertLastResult("1000");
process("2 km/h m/h");
assertLastResult("2000");
process("1+1 * 8 / 2 + 10km/h m/h");
assertLastResult("15000");
process("km/h");
assertLastResult("1000");
}
@Test
public void testSplitParameters() {
assertSplitParameters(new String[] {}, null);
......
......@@ -55,6 +55,13 @@ public class TestUnitConverter {
ResourceLoader.processResource("units/default.units", unitConverter::loadUnit);
ResourceLoader.processResource("units/default.conversions", unitConverter::loadConversion);
Assert.assertNull(unitConverter.getCompoundUnit(null));
Assert.assertNull(unitConverter.getCompoundUnit(""));
Assert.assertNull(unitConverter.getCompoundUnit(" "));
Assert.assertNull(unitConverter.getCompoundUnit("12"));
Assert.assertNull(unitConverter.getCompoundUnit("12 / 12"));
Assert.assertNull(unitConverter.getCompoundUnit("km/2/h"));
assertEquals(new BigDecimal("0.0001726031089548149915603983845453662"), unitConverter.convert("km/h", "ml/sec", new BigDecimal("1"), DEFAULT_MATH_CONTEXT));
assertEquals(new BigDecimal("60"), unitConverter.convert("l/min", "l/h", new BigDecimal("1"), DEFAULT_MATH_CONTEXT));
assertEquals(new BigDecimal("127137.6"), unitConverter.convert("m/s^2", "km/h^2", new BigDecimal("9.81"), DEFAULT_MATH_CONTEXT));
......