package edu.stanford.nlp.time;
import edu.stanford.nlp.ling.CoreAnnotations;
import edu.stanford.nlp.ling.tokensregex.*;
import edu.stanford.nlp.ling.tokensregex.types.*;
import edu.stanford.nlp.pipeline.ChunkAnnotationUtils;
import edu.stanford.nlp.pipeline.CoreMapAttributeAggregator;
import edu.stanford.nlp.util.CoreMap;
import edu.stanford.nlp.util.StringUtils;
import java.util.List;
import java.util.regex.Pattern;
/**
* Provides generic mechanism to convert natural language into temporal representation
* by reading patterns/rules specifying mapping between text and temporal objects
* from file.
*
* @author Angel Chang
*/
public class GenericTimeExpressionPatterns implements TimeExpressionPatterns {
Env env;
Options options;
public GenericTimeExpressionPatterns(Options options) {
this.options = options;
initEnv();
if (options.binders != null) {
for (Env.Binder binder:options.binders) {
binder.bind(env);
}
}
}
public CoreMapExpressionExtractor createExtractor() {
List<String> filenames = StringUtils.split(options.grammarFilename, "\\s*[,;]\\s*");
return CoreMapExpressionExtractor.createExtractorFromFiles(env, filenames);
}
private static class TimexTypeMatchNodePattern extends NodePattern<TimeExpression> {
SUTime.TimexType type;
public TimexTypeMatchNodePattern(SUTime.TimexType type) { this.type = type; }
public boolean match(TimeExpression te) {
if (te != null) {
SUTime.Temporal t = te.getTemporal();
if (t != null) {
return type.equals(t.getTimexType());
}
}
return false;
}
}
private static class MatchedExpressionValueTypeMatchNodePattern extends NodePattern<MatchedExpression> {
String valueType;
public MatchedExpressionValueTypeMatchNodePattern(String valueType) { this.valueType = valueType; }
public boolean match(MatchedExpression me) {
Value v = (me != null)? me.getValue():null;
if (v != null) {
return (valueType.equals(v.getType()));
}
return false;
}
}
private void initEnv()
{
env = TokenSequencePattern.getNewEnv();
env.setDefaultResultsAnnotationExtractor(TimeExpression.TimeExpressionConverter);
env.setDefaultTokensAnnotationKey(CoreAnnotations.NumerizedTokensAnnotation.class);
env.setDefaultResultAnnotationKey(TimeExpression.Annotation.class);
env.setDefaultNestedResultsAnnotationKey(TimeExpression.ChildrenAnnotation.class);
env.setDefaultTokensAggregators(CoreMapAttributeAggregator.DEFAULT_NUMERIC_TOKENS_AGGREGATORS);
env.bind("nested", TimeExpression.ChildrenAnnotation.class);
env.bind("time", new TimeFormatter.TimePatternExtractRuleCreator());
// Do case insensitive matching
env.setDefaultStringPatternFlags(Pattern.CASE_INSENSITIVE);
env.bind("options", options);
env.bind("TIME_REF", SUTime.TIME_REF);
env.bind("TIME_REF_UNKNOWN", SUTime.TIME_REF_UNKNOWN);
env.bind("TIME_UNKNOWN", SUTime.TIME_UNKNOWN);
env.bind("TIME_NONE", SUTime.TIME_NONE);
env.bind("ERA_AD", SUTime.ERA_AD);
env.bind("ERA_BC", SUTime.ERA_BC);
env.bind("ERA_UNKNOWN", SUTime.ERA_UNKNOWN);
env.bind("HALFDAY_AM", SUTime.HALFDAY_AM);
env.bind("HALFDAY_PM", SUTime.HALFDAY_PM);
env.bind("HALFDAY_UNKNOWN", SUTime.HALFDAY_UNKNOWN);
env.bind("RESOLVE_TO_THIS", SUTime.RESOLVE_TO_THIS);
env.bind("RESOLVE_TO_PAST", SUTime.RESOLVE_TO_PAST);
env.bind("RESOLVE_TO_FUTURE", SUTime.RESOLVE_TO_FUTURE);
env.bind("RESOLVE_TO_CLOSEST", SUTime.RESOLVE_TO_CLOSEST);
env.bind("numcomptype", CoreAnnotations.NumericCompositeTypeAnnotation.class);
env.bind("numcompvalue", CoreAnnotations.NumericCompositeValueAnnotation.class);
env.bind("temporal", TimeExpression.Annotation.class);
// env.bind("tags", SequenceMatchRules.Tags.TagsAnnotation.class);
env.bind("::IS_TIMEX_DATE", new TimexTypeMatchNodePattern(SUTime.TimexType.DATE));
env.bind("::IS_TIMEX_DURATION", new TimexTypeMatchNodePattern(SUTime.TimexType.DURATION));
env.bind("::IS_TIMEX_TIME", new TimexTypeMatchNodePattern(SUTime.TimexType.TIME));
env.bind("::IS_TIMEX_SET", new TimexTypeMatchNodePattern(SUTime.TimexType.SET));
env.bind("::IS_TIME_UNIT", new MatchedExpressionValueTypeMatchNodePattern("TIMEUNIT"));
env.bind("::MONTH", new MatchedExpressionValueTypeMatchNodePattern("MONTH_OF_YEAR"));
env.bind("::DAYOFWEEK", new MatchedExpressionValueTypeMatchNodePattern("DAY_OF_WEEK"));
// BINDINGS for parsing from file!!!!!!!
for (SUTime.TemporalOp t:SUTime.TemporalOp.values()) {
env.bind(t.name(), new Expressions.PrimitiveValue<>("TemporalOp", t));
}
for (SUTime.TimeUnit t: SUTime.TimeUnit.values()) {
if (!t.equals(SUTime.TimeUnit.UNKNOWN)) {
//env.bind(t.name(), new SequenceMatchRules.PrimitiveValue<SUTime.Temporal>("DURATION", t.getDuration(), "TIMEUNIT"));
env.bind(t.name(), new Expressions.PrimitiveValue<SUTime.Temporal>("TIMEUNIT", t.getDuration()));
}
}
for (SUTime.StandardTemporalType t: SUTime.StandardTemporalType.values()) {
env.bind(t.name(), new Expressions.PrimitiveValue<>("TemporalType", t));
}
env.bind("Duration", new Expressions.PrimitiveValue<ValueFunction>(
Expressions.TYPE_FUNCTION,
new ValueFunctions.NamedValueFunction("Duration") {
private SUTime.Temporal addEndPoints(SUTime.Duration d, SUTime.Time beginTime, SUTime.Time endTime)
{
SUTime.Temporal t = d;
if (d != null && (beginTime != null || endTime != null)) {
SUTime.Time b = beginTime;
SUTime.Time e = endTime;
// New so we get different time ids
if (b == SUTime.TIME_REF_UNKNOWN) {
b = new SUTime.RefTime("UNKNOWN");
} else if (b == SUTime.TIME_UNKNOWN) {
b = new SUTime.SimpleTime("UNKNOWN");
}
if (e == SUTime.TIME_REF_UNKNOWN) {
e = new SUTime.RefTime("UNKNOWN");
} else if (e == SUTime.TIME_UNKNOWN) {
e = new SUTime.SimpleTime("UNKNOWN");
}
t = new SUTime.Range(b,e,d);
}
return t;
}
public boolean checkArgs(List<Value> in) {
// TODO: Check args
return true;
}
public Value apply(Env env, List<Value> in) {
if (in.size() == 2) {
SUTime.Duration d = (SUTime.Duration) in.get(0).get();
if (in.get(1).get() instanceof Number) {
int m = ((Number) in.get(1).get()).intValue();
return new Expressions.PrimitiveValue("DURATION", d.multiplyBy(m));
} else if (in.get(1).get() instanceof String){
Number n = Integer.parseInt((String) in.get(1).get());
if (n != null) {
return new Expressions.PrimitiveValue("DURATION", d.multiplyBy(n.intValue()));
} else {
return null;
}
} else {
throw new IllegalArgumentException("Invalid arguments to " + name);
}
} else if (in.size() == 5 || in.size() == 3) {
// TODO: Handle Strings...
List<? extends CoreMap> durationStartTokens = (List<? extends CoreMap>) in.get(0).get();
Number durationStartVal = (durationStartTokens != null)? durationStartTokens.get(0).get(CoreAnnotations.NumericCompositeValueAnnotation.class):null;
List<? extends CoreMap> durationEndTokens = (List<? extends CoreMap>) in.get(1).get();
Number durationEndVal = (durationEndTokens != null)? durationEndTokens.get(0).get(CoreAnnotations.NumericCompositeValueAnnotation.class):null;
// TODO: This should already be in durations....
List<? extends CoreMap> durationUnitTokens = (List<? extends CoreMap>) in.get(2).get();
//String durationUnitString = (durationUnitTokens != null)? durationUnitTokens.get(0).get(CoreAnnotations.TextAnnotation.class):null;
//SUTime.Duration durationUnit = getDuration(durationUnitString);
TimeExpression te = (durationUnitTokens != null)? durationUnitTokens.get(0).get(TimeExpression.Annotation.class):null;
SUTime.Duration durationUnit = (SUTime.Duration) te.getTemporal();
// TODO: Handle inexactness
// Create duration range...
SUTime.Duration durationStart = (durationStartVal != null)? durationUnit.multiplyBy(durationStartVal.intValue()):null;
SUTime.Duration durationEnd = (durationEndVal != null)? durationUnit.multiplyBy(durationEndVal.intValue()):null;
SUTime.Duration duration = durationStart;
if (duration == null) {
if (durationEnd != null) {
duration = durationEnd;
} else {
duration = new SUTime.InexactDuration(durationUnit);
}
}
else if (durationEnd != null) { duration = new SUTime.DurationRange(durationStart, durationEnd); }
// Add begin and end times
SUTime.Time beginTime = (in.size() > 3)? (SUTime.Time) in.get(3).get():null;
SUTime.Time endTime = (in.size() > 4)? (SUTime.Time) in.get(4).get():null;
SUTime.Temporal temporal = addEndPoints(duration, beginTime, endTime);
if (temporal instanceof SUTime.Range) {
return new Expressions.PrimitiveValue("RANGE", temporal);
} else {
return new Expressions.PrimitiveValue("DURATION", temporal);
}
} else {
throw new IllegalArgumentException("Invalid number of arguments to " + name);
}
}
}
));
env.bind("DayOfWeek", new Expressions.PrimitiveValue<ValueFunction>(
Expressions.TYPE_FUNCTION,
new ValueFunctions.NamedValueFunction("DayOfWeek") {
public boolean checkArgs(List<Value> in) {
if (in.size() != 1) {
return false;
}
if (in.get(0) == null || !(in.get(0).get() instanceof Number)) {
return false;
}
return true;
}
public Value apply(Env env, List<Value> in) {
if (in.size() == 1) {
return new Expressions.PrimitiveValue(SUTime.StandardTemporalType.DAY_OF_WEEK.name(),
SUTime.StandardTemporalType.DAY_OF_WEEK.createTemporal(((Number) in.get(0).get()).intValue()));
} else {
throw new IllegalArgumentException("Invalid number of arguments to " + name);
}
}
}
));
env.bind("MonthOfYear", new Expressions.PrimitiveValue<ValueFunction>(
Expressions.TYPE_FUNCTION,
new ValueFunctions.NamedValueFunction("MonthOfYear") {
public boolean checkArgs(List<Value> in) {
if (in.size() != 1) {
return false;
}
if (in.get(0) == null || !(in.get(0).get() instanceof Number)) {
return false;
}
return true;
}
public Value apply(Env env, List<Value> in) {
if (in.size() == 1) {
return new Expressions.PrimitiveValue(SUTime.StandardTemporalType.MONTH_OF_YEAR.name(),
SUTime.StandardTemporalType.MONTH_OF_YEAR.createTemporal(((Number) in.get(0).get()).intValue()));
} else {
throw new IllegalArgumentException("Invalid number of arguments to " + name);
}
}
}
));
env.bind("MakePeriodicTemporalSet", new Expressions.PrimitiveValue<ValueFunction>(
Expressions.TYPE_FUNCTION,
new ValueFunctions.NamedValueFunction("MakePeriodicTemporalSet") {
// First argument is the temporal acting as the base of the periodic set
// Second argument is the quantifier (string)
// Third argument is the multiple (how much to scale the natural period)
public boolean checkArgs(List<Value> in) {
if (in.size() < 3) {
return false;
}
if (in.get(0) == null ||
(!(in.get(0).get() instanceof SUTime.Temporal) && !(in.get(0).get() instanceof TimeExpression))) {
return false;
}
if (in.get(1) == null ||
(!(in.get(1).get() instanceof String) && !(in.get(1).get() instanceof List))) {
return false;
}
if (in.get(2) == null || !(in.get(2).get() instanceof Number)) {
return false;
}
return true;
}
public Value apply(Env env, List<Value> in) {
if (in.size() >= 1) {
SUTime.Temporal temporal = null;
Object t = in.get(0).get();
if (t instanceof SUTime.Temporal) {
temporal = (SUTime.Temporal) in.get(0).get();
} else if (t instanceof TimeExpression) {
temporal = ((TimeExpression) t).getTemporal();
} else {
throw new IllegalArgumentException("Type mismatch on arg0: Cannot apply " + this + " to " + in);
}
String quant = null;
int scale = 1;
if (in.size() >= 2 && in.get(1) != null) {
Object arg1 = in.get(1).get();
if (arg1 instanceof String) {
quant = (String) arg1;
} else if (arg1 instanceof List) {
List<CoreMap> cms = (List<CoreMap>) arg1;
quant = ChunkAnnotationUtils.getTokenText(cms, CoreAnnotations.TextAnnotation.class);
if (quant != null) {
quant = quant.toLowerCase();
}
} else {
throw new IllegalArgumentException("Type mismatch on arg1: Cannot apply " + this + " to " + in);
}
}
if (in.size() >= 3 && in.get(2) != null) {
Number arg2 = (Number) in.get(2).get();
if (arg2 != null) {
scale = arg2.intValue();
}
}
SUTime.Duration period = temporal.getPeriod();
if (period != null && scale != 1) {
period = period.multiplyBy(scale);
}
return new Expressions.PrimitiveValue("PeriodicTemporalSet",
new SUTime.PeriodicTemporalSet(temporal,period,quant,null/*"P1X"*/));
} else {
throw new IllegalArgumentException("Invalid number of arguments to " + name);
}
}
}
));
env.bind("TemporalCompose", new Expressions.PrimitiveValue<ValueFunction>(
Expressions.TYPE_FUNCTION,
new ValueFunctions.NamedValueFunction("TemporalCompose") {
public boolean checkArgs(List<Value> in) {
if (in.size() < 1) {
return false;
}
if (in.get(0) == null || !(in.get(0).get() instanceof SUTime.TemporalOp)) {
return false;
}
return true;
}
public Value apply(Env env, List<Value> in) {
if (in.size() > 1) {
SUTime.TemporalOp op = (SUTime.TemporalOp) in.get(0).get();
boolean allTemporalArgs = true;
Object[] args = new Object[in.size()-1];
for (int i = 0; i < args.length; i++) {
Value v = in.get(i+1);
if (v != null) {
args[i] = v.get();
if (args[i] instanceof MatchedExpression) {
Value v2 = ((MatchedExpression) args[i]).getValue();
args[i] = (v2 != null)? v2.get():null;
}
if (args[i] != null && !(args[i] instanceof SUTime.Temporal)) {
allTemporalArgs = false;
}
}
}
if (allTemporalArgs) {
SUTime.Temporal[] temporalArgs = new SUTime.Temporal[args.length];
for (int i = 0; i < args.length; i++) {
temporalArgs[i] = (SUTime.Temporal) args[i];
}
return new Expressions.PrimitiveValue(null, op.apply(temporalArgs));
} else {
return new Expressions.PrimitiveValue(null, op.apply(args));
}
} else {
throw new IllegalArgumentException("Invalid number of arguments to " + name);
}
}
}
));
}
public int determineRelFlags(CoreMap annotation, TimeExpression te)
{
int flags = 0;
boolean flagsSet = false;
if (te.value.getTags() != null) {
Value v = te.value.getTags().getTag("resolveTo");
if (v != null && v.get() instanceof Number) {
flags = ((Number) v.get()).intValue();
flagsSet = true;
}
}
if (!flagsSet) {
if (te.getTemporal() instanceof SUTime.PartialTime) {
flags = SUTime.RESOLVE_TO_CLOSEST;
}
}
return flags;
}
}