/*
* The contents of this file are subject to the Mozilla Public License
* Version 1.1 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
* License for the specific language governing rights and limitations
* under the License.
*
* The Original Code is "Simplenlg".
*
* The Initial Developer of the Original Code is Ehud Reiter, Albert Gatt and Dave Westwater.
* Portions created by Ehud Reiter, Albert Gatt and Dave Westwater are Copyright (C) 2010-11 The University of Aberdeen. All Rights Reserved.
*
* Contributor(s): Ehud Reiter, Albert Gatt, Dave Wewstwater, Roman Kutlak, Margaret Mitchell.
*/
package simplenlg.syntax.english;
import java.util.Iterator;
import java.util.List;
import java.util.Stack;
import simplenlg.features.DiscourseFunction;
import simplenlg.features.Feature;
import simplenlg.features.Form;
import simplenlg.features.InternalFeature;
import simplenlg.features.InterrogativeType;
import simplenlg.features.NumberAgreement;
import simplenlg.features.Tense;
import simplenlg.framework.CoordinatedPhraseElement;
import simplenlg.framework.InflectedWordElement;
import simplenlg.framework.LexicalCategory;
import simplenlg.framework.ListElement;
import simplenlg.framework.NLGElement;
import simplenlg.framework.NLGFactory;
import simplenlg.framework.PhraseCategory;
import simplenlg.framework.PhraseElement;
import simplenlg.framework.StringElement;
import simplenlg.framework.WordElement;
import simplenlg.phrasespec.SPhraseSpec;
/**
* <p>
* This class contains static methods to help the syntax processor realise verb
* phrases. It adds auxiliary verbs into the element tree as required.
* </p>
*
* @author D. Westwater, University of Aberdeen.
* @version 4.0
*/
abstract class VerbPhraseHelper {
/**
* The main method for realising verb phrases.
*
* @param parent
* the <code>SyntaxProcessor</code> that called this method.
* @param phrase
* the <code>PhraseElement</code> to be realised.
* @return the realised <code>NLGElement</code>.
*/
static NLGElement realise(SyntaxProcessor parent, PhraseElement phrase) {
ListElement realisedElement = null;
Stack<NLGElement> vgComponents = null;
Stack<NLGElement> mainVerbRealisation = new Stack<NLGElement>();
Stack<NLGElement> auxiliaryRealisation = new Stack<NLGElement>();
if (phrase != null) {
vgComponents = createVerbGroup(parent, phrase);
splitVerbGroup(vgComponents, mainVerbRealisation,
auxiliaryRealisation);
realisedElement = new ListElement();
if (!phrase.hasFeature(InternalFeature.REALISE_AUXILIARY)
|| phrase.getFeatureAsBoolean(
InternalFeature.REALISE_AUXILIARY).booleanValue()) {
realiseAuxiliaries(parent, realisedElement,
auxiliaryRealisation);
PhraseHelper.realiseList(parent, realisedElement, phrase
.getPreModifiers(), DiscourseFunction.PRE_MODIFIER);
realiseMainVerb(parent, phrase, mainVerbRealisation,
realisedElement);
} else if (isCopular(phrase.getHead())) {
realiseMainVerb(parent, phrase, mainVerbRealisation,
realisedElement);
PhraseHelper.realiseList(parent, realisedElement, phrase
.getPreModifiers(), DiscourseFunction.PRE_MODIFIER);
} else {
PhraseHelper.realiseList(parent, realisedElement, phrase
.getPreModifiers(), DiscourseFunction.PRE_MODIFIER);
realiseMainVerb(parent, phrase, mainVerbRealisation,
realisedElement);
}
realiseComplements(parent, phrase, realisedElement);
PhraseHelper.realiseList(parent, realisedElement, phrase
.getPostModifiers(), DiscourseFunction.POST_MODIFIER);
}
return realisedElement;
}
/**
* Realises the auxiliary verbs in the verb group.
*
* @param parent
* the parent <code>SyntaxProcessor</code> that will do the
* realisation of the complementiser.
* @param realisedElement
* the current realisation of the noun phrase.
* @param auxiliaryRealisation
* the stack of auxiliary verbs.
*/
private static void realiseAuxiliaries(SyntaxProcessor parent,
ListElement realisedElement, Stack<NLGElement> auxiliaryRealisation) {
NLGElement aux = null;
NLGElement currentElement = null;
while (!auxiliaryRealisation.isEmpty()) {
aux = auxiliaryRealisation.pop();
currentElement = parent.realise(aux);
if (currentElement != null) {
realisedElement.addComponent(currentElement);
currentElement.setFeature(InternalFeature.DISCOURSE_FUNCTION,
DiscourseFunction.AUXILIARY);
}
}
}
/**
* Realises the main group of verbs in the phrase.
*
* @param parent
* the parent <code>SyntaxProcessor</code> that will do the
* realisation of the complementiser.
* @param phrase
* the <code>PhraseElement</code> representing this noun phrase.
* @param mainVerbRealisation
* the stack of the main verbs in the phrase.
* @param realisedElement
* the current realisation of the noun phrase.
*/
private static void realiseMainVerb(SyntaxProcessor parent,
PhraseElement phrase, Stack<NLGElement> mainVerbRealisation,
ListElement realisedElement) {
NLGElement currentElement = null;
NLGElement main = null;
while (!mainVerbRealisation.isEmpty()) {
main = mainVerbRealisation.pop();
main.setFeature(Feature.INTERROGATIVE_TYPE, phrase
.getFeature(Feature.INTERROGATIVE_TYPE));
currentElement = parent.realise(main);
if (currentElement != null) {
realisedElement.addComponent(currentElement);
}
}
}
/**
* Realises the complements of this phrase.
*
* @param parent
* the parent <code>SyntaxProcessor</code> that will do the
* realisation of the complementiser.
* @param phrase
* the <code>PhraseElement</code> representing this noun phrase.
* @param realisedElement
* the current realisation of the noun phrase.
*/
private static void realiseComplements(SyntaxProcessor parent,
PhraseElement phrase, ListElement realisedElement) {
ListElement indirects = new ListElement();
ListElement directs = new ListElement();
ListElement unknowns = new ListElement();
Object discourseValue = null;
NLGElement currentElement = null;
for (NLGElement complement : phrase
.getFeatureAsElementList(InternalFeature.COMPLEMENTS)) {
discourseValue = complement
.getFeature(InternalFeature.DISCOURSE_FUNCTION);
currentElement = parent.realise(complement);
if (currentElement != null) {
currentElement.setFeature(InternalFeature.DISCOURSE_FUNCTION,
DiscourseFunction.COMPLEMENT);
if (DiscourseFunction.INDIRECT_OBJECT.equals(discourseValue)) {
indirects.addComponent(currentElement);
} else if (DiscourseFunction.OBJECT.equals(discourseValue)) {
directs.addComponent(currentElement);
} else {
unknowns.addComponent(currentElement);
}
}
}
if (!InterrogativeType.isIndirectObject(phrase
.getFeature(Feature.INTERROGATIVE_TYPE))) {
realisedElement.addComponents(indirects.getChildren());
}
if (!phrase.getFeatureAsBoolean(Feature.PASSIVE).booleanValue()) {
if (!InterrogativeType.isObject(phrase
.getFeature(Feature.INTERROGATIVE_TYPE))) {
realisedElement.addComponents(directs.getChildren());
}
realisedElement.addComponents(unknowns.getChildren());
}
}
/**
* Splits the stack of verb components into two sections. One being the verb
* associated with the main verb group, the other being associated with the
* auxiliary verb group.
*
* @param vgComponents
* the stack of verb components in the verb group.
* @param mainVerbRealisation
* the main group of verbs.
* @param auxiliaryRealisation
* the auxiliary group of verbs.
*/
private static void splitVerbGroup(Stack<NLGElement> vgComponents,
Stack<NLGElement> mainVerbRealisation,
Stack<NLGElement> auxiliaryRealisation) {
boolean mainVerbSeen = false;
for (NLGElement word : vgComponents) {
if (!mainVerbSeen) {
mainVerbRealisation.push(word);
if (!word.equals("not")) { //$NON-NLS-1$
mainVerbSeen = true;
}
} else {
auxiliaryRealisation.push(word);
}
}
}
/**
* Creates a stack of verbs for the verb phrase. Additional auxiliary verbs
* are added as required based on the features of the verb phrase.
*
* @param parent
* the parent <code>SyntaxProcessor</code> that will do the
* realisation of the complementiser.
* @param phrase
* the <code>PhraseElement</code> representing this noun phrase.
* @return the verb group as a <code>Stack</code> of <code>NLGElement</code>
* s.
*/
@SuppressWarnings("deprecation")
static final private Stack<NLGElement> createVerbGroup(
SyntaxProcessor parent, PhraseElement phrase) {
String actualModal = null;
Object formValue = phrase.getFeature(Feature.FORM);
Tense tenseValue = phrase.getTense();
String modal = phrase.getFeatureAsString(Feature.MODAL);
boolean modalPast = false;
Stack<NLGElement> vgComponents = new Stack<NLGElement>();
boolean interrogative = phrase.hasFeature(Feature.INTERROGATIVE_TYPE);
if (Form.GERUND.equals(formValue) || Form.INFINITIVE.equals(formValue)) {
tenseValue = Tense.PRESENT;
}
if (Form.INFINITIVE.equals(formValue)) {
actualModal = "to"; //$NON-NLS-1$
} else if (formValue == null || Form.NORMAL.equals(formValue)) {
if (Tense.FUTURE.equals(tenseValue)
&& modal == null
&& ((!(phrase.getHead() instanceof CoordinatedPhraseElement)) || (phrase
.getHead() instanceof CoordinatedPhraseElement && interrogative))) {
actualModal = "will"; //$NON-NLS-1$
} else if (modal != null) {
actualModal = modal;
if (Tense.PAST.equals(tenseValue)) {
modalPast = true;
}
}
}
pushParticles(phrase, parent, vgComponents);
NLGElement frontVG = grabHeadVerb(phrase, tenseValue, modal != null);
checkImperativeInfinitive(formValue, frontVG);
if (phrase.getFeatureAsBoolean(Feature.PASSIVE).booleanValue()) {
frontVG = addBe(frontVG, vgComponents, Form.PAST_PARTICIPLE);
}
if (phrase.getFeatureAsBoolean(Feature.PROGRESSIVE).booleanValue()) {
frontVG = addBe(frontVG, vgComponents, Form.PRESENT_PARTICIPLE);
}
if (phrase.getFeatureAsBoolean(Feature.PERFECT).booleanValue()
|| modalPast) {
frontVG = addHave(frontVG, vgComponents, modal, tenseValue);
}
frontVG = pushIfModal(actualModal != null, phrase, frontVG,
vgComponents);
frontVG = createNot(phrase, vgComponents, frontVG, modal != null);
if (frontVG != null) {
pushFrontVerb(phrase, vgComponents, frontVG, formValue,
interrogative);
}
pushModal(actualModal, phrase, vgComponents);
return vgComponents;
}
/**
* Pushes the modal onto the stack of verb components.
*
* @param actualModal
* the modal to be used.
* @param phrase
* the <code>PhraseElement</code> representing this noun phrase.
* @param vgComponents
* the stack of verb components in the verb group.
*/
private static void pushModal(String actualModal, PhraseElement phrase,
Stack<NLGElement> vgComponents) {
if (actualModal != null
&& !phrase.getFeatureAsBoolean(InternalFeature.IGNORE_MODAL)
.booleanValue()) {
vgComponents.push(new InflectedWordElement(actualModal,
LexicalCategory.MODAL));
}
}
/**
* Pushes the front verb onto the stack of verb components.
*
* @param phrase
* the <code>PhraseElement</code> representing this noun phrase.
* @param vgComponents
* the stack of verb components in the verb group.
* @param frontVG
* the first verb in the verb group.
* @param formValue
* the <code>Form</code> of the phrase.
* @param interrogative
* <code>true</code> if the phrase is interrogative.
*/
private static void pushFrontVerb(PhraseElement phrase,
Stack<NLGElement> vgComponents, NLGElement frontVG,
Object formValue, boolean interrogative) {
if (Form.GERUND.equals(formValue)) {
frontVG.setFeature(Feature.FORM, Form.PRESENT_PARTICIPLE);
vgComponents.push(frontVG);
} else if (Form.PAST_PARTICIPLE.equals(formValue)) {
frontVG.setFeature(Feature.FORM, Form.PAST_PARTICIPLE);
vgComponents.push(frontVG);
} else if (Form.PRESENT_PARTICIPLE.equals(formValue)) {
frontVG.setFeature(Feature.FORM, Form.PRESENT_PARTICIPLE);
vgComponents.push(frontVG);
} else if ((!(formValue == null || Form.NORMAL.equals(formValue)) || interrogative)
&& !isCopular(phrase.getHead()) && vgComponents.isEmpty()) {
// AG: fix below: if interrogative, only set non-morph feature in
// case it's not WHO_SUBJECT OR WHAT_SUBJECT
Object interrogType = phrase.getFeature(Feature.INTERROGATIVE_TYPE);
if (!(InterrogativeType.WHO_SUBJECT.equals(interrogType) || InterrogativeType.WHAT_SUBJECT
.equals(interrogType))) {
frontVG.setFeature(InternalFeature.NON_MORPH, true);
}
vgComponents.push(frontVG);
} else {
NumberAgreement numToUse = determineNumber(phrase.getParent(),
phrase);
frontVG.setFeature(Feature.TENSE, phrase.getFeature(Feature.TENSE));
frontVG.setFeature(Feature.PERSON, phrase
.getFeature(Feature.PERSON));
frontVG.setFeature(Feature.NUMBER, numToUse);
vgComponents.push(frontVG);
}
}
/**
* Adds <em>not</em> to the stack if the phrase is negated.
*
* @param phrase
* the <code>PhraseElement</code> representing this noun phrase.
* @param vgComponents
* the stack of verb components in the verb group.
* @param frontVG
* the first verb in the verb group.
* @param hasModal
* the phrase has a modal
* @return the new element for the front of the group.
*/
private static NLGElement createNot(PhraseElement phrase,
Stack<NLGElement> vgComponents, NLGElement frontVG, boolean hasModal) {
NLGElement newFront = frontVG;
if (phrase.getFeatureAsBoolean(Feature.NEGATED).booleanValue()) {
NLGFactory factory = phrase.getFactory();
if (!vgComponents.empty() || frontVG != null && isCopular(frontVG)) {
vgComponents.push(new InflectedWordElement(
"not", LexicalCategory.ADVERB)); //$NON-NLS-1$
} else {
if (frontVG != null && !hasModal) {
frontVG.setFeature(Feature.NEGATED, true);
vgComponents.push(frontVG);
}
vgComponents.push(new InflectedWordElement(
"not", LexicalCategory.ADVERB)); //$NON-NLS-1$
if (factory != null) {
newFront = factory.createInflectedWord("do",
LexicalCategory.VERB);
} else {
newFront = new InflectedWordElement(
"do", LexicalCategory.VERB); //$NON-NLS-1$
}
}
}
return newFront;
}
/**
* Pushes the front verb on to the stack if the phrase has a modal.
*
* @param hasModal
* the phrase has a modal
* @param phrase
* the <code>PhraseElement</code> representing this noun phrase.
* @param frontVG
* the first verb in the verb group.
* @param vgComponents
* the stack of verb components in the verb group.
* @return the new element for the front of the group.
*/
private static NLGElement pushIfModal(boolean hasModal,
PhraseElement phrase, NLGElement frontVG,
Stack<NLGElement> vgComponents) {
NLGElement newFront = frontVG;
if (hasModal
&& !phrase.getFeatureAsBoolean(InternalFeature.IGNORE_MODAL)
.booleanValue()) {
if (frontVG != null) {
frontVG.setFeature(InternalFeature.NON_MORPH, true);
vgComponents.push(frontVG);
}
newFront = null;
}
return newFront;
}
/**
* Adds <em>have</em> to the stack.
*
* @param frontVG
* the first verb in the verb group.
* @param vgComponents
* the stack of verb components in the verb group.
* @param modal
* the modal to be used.
* @param tenseValue
* the <code>Tense</code> of the phrase.
* @return the new element for the front of the group.
*/
private static NLGElement addHave(NLGElement frontVG,
Stack<NLGElement> vgComponents, String modal, Tense tenseValue) {
NLGElement newFront = frontVG;
if (frontVG != null) {
frontVG.setFeature(Feature.FORM, Form.PAST_PARTICIPLE);
vgComponents.push(frontVG);
}
newFront = new InflectedWordElement("have", LexicalCategory.VERB); //$NON-NLS-1$
newFront.setFeature(Feature.TENSE, tenseValue);
if (modal != null) {
newFront.setFeature(InternalFeature.NON_MORPH, true);
}
return newFront;
}
/**
* Adds the <em>be</em> verb to the front of the group.
*
* @param frontVG
* the first verb in the verb group.
* @param vgComponents
* the stack of verb components in the verb group.
* @param frontForm
* the form the current front verb is to take.
* @return the new element for the front of the group.
*/
private static NLGElement addBe(NLGElement frontVG,
Stack<NLGElement> vgComponents, Form frontForm) {
if (frontVG != null) {
frontVG.setFeature(Feature.FORM, frontForm);
vgComponents.push(frontVG);
}
return new InflectedWordElement("be", LexicalCategory.VERB); //$NON-NLS-1$
}
/**
* Checks to see if the phrase is in imperative, infinitive or bare
* infinitive form. If it is then no morphology is done on the main verb.
*
* @param formValue
* the <code>Form</code> of the phrase.
* @param frontVG
* the first verb in the verb group.
*/
private static void checkImperativeInfinitive(Object formValue,
NLGElement frontVG) {
if ((Form.IMPERATIVE.equals(formValue)
|| Form.INFINITIVE.equals(formValue) || Form.BARE_INFINITIVE
.equals(formValue))
&& frontVG != null) {
frontVG.setFeature(InternalFeature.NON_MORPH, true);
}
}
/**
* Grabs the head verb of the verb phrase and sets it to future tense if the
* phrase is future tense. It also turns off negation if the group has a
* modal.
*
* @param phrase
* the <code>PhraseElement</code> representing this noun phrase.
* @param tenseValue
* the <code>Tense</code> of the phrase.
* @param hasModal
* <code>true</code> if the verb phrase has a modal.
* @return the modified head element
*/
private static NLGElement grabHeadVerb(PhraseElement phrase,
Tense tenseValue, boolean hasModal) {
NLGElement frontVG = phrase.getHead();
if (frontVG != null) {
if (frontVG instanceof WordElement) {
frontVG = new InflectedWordElement((WordElement) frontVG);
}
//AG: tense value should always be set on frontVG
if (tenseValue != null) {
frontVG.setFeature(Feature.TENSE, tenseValue);
}
// if (Tense.FUTURE.equals(tenseValue) && frontVG != null) {
// frontVG.setFeature(Feature.TENSE, Tense.FUTURE);
// }
if (hasModal) {
frontVG.setFeature(Feature.NEGATED, false);
}
}
return frontVG;
}
/**
* Pushes the particles of the main verb onto the verb group stack.
*
* @param phrase
* the <code>PhraseElement</code> representing this noun phrase.
* @param parent
* the parent <code>SyntaxProcessor</code> that will do the
* realisation of the complementiser.
* @param vgComponents
* the stack of verb components in the verb group.
*/
private static void pushParticles(PhraseElement phrase,
SyntaxProcessor parent, Stack<NLGElement> vgComponents) {
Object particle = phrase.getFeature(Feature.PARTICLE);
if (particle instanceof String) {
vgComponents.push(new StringElement((String) particle));
} else if (particle instanceof NLGElement) {
vgComponents.push(parent.realise((NLGElement) particle));
}
}
/**
* Determines the number agreement for the phrase ensuring that any number
* agreement on the parent element is inherited by the phrase.
*
* @param parent
* the parent element of the phrase.
* @param phrase
* the <code>PhraseElement</code> representing this noun phrase.
* @return the <code>NumberAgreement</code> to be used for the phrase.
*/
private static NumberAgreement determineNumber(NLGElement parent,
PhraseElement phrase) {
Object numberValue = phrase.getFeature(Feature.NUMBER);
NumberAgreement number = null;
if (numberValue != null && numberValue instanceof NumberAgreement) {
number = (NumberAgreement) numberValue;
} else {
number = NumberAgreement.SINGULAR;
}
// Ehud Reiter = modified below to force number from VP for WHAT_SUBJECT and WHO_SUBJECT interrogatuves
if (parent instanceof PhraseElement) {
if (parent.isA(PhraseCategory.CLAUSE)
&& (PhraseHelper.isExpletiveSubject((PhraseElement) parent) ||
InterrogativeType.WHO_SUBJECT.equals(parent.getFeature(Feature.INTERROGATIVE_TYPE)) ||
InterrogativeType.WHAT_SUBJECT.equals(parent.getFeature(Feature.INTERROGATIVE_TYPE)))
&& isCopular(phrase.getHead())) {
if (hasPluralComplement(phrase
.getFeatureAsElementList(InternalFeature.COMPLEMENTS))) {
number = NumberAgreement.PLURAL;
} else {
number = NumberAgreement.SINGULAR;
}
}
}
return number;
}
/**
* Checks to see if any of the complements to the phrase are plural.
*
* @param complements
* the list of complements of the phrase.
* @return <code>true</code> if any of the complements are plural.
*/
private static boolean hasPluralComplement(List<NLGElement> complements) {
boolean plural = false;
Iterator<NLGElement> complementIterator = complements.iterator();
NLGElement eachComplement = null;
Object numberValue = null;
while (complementIterator.hasNext() && !plural) {
eachComplement = complementIterator.next();
if (eachComplement != null
&& eachComplement.isA(PhraseCategory.NOUN_PHRASE)) {
numberValue = eachComplement.getFeature(Feature.NUMBER);
if (numberValue != null
&& NumberAgreement.PLURAL.equals(numberValue)) {
plural = true;
}
}
}
return plural;
}
/**
* Checks to see if the base form of the word is copular, i.e. <em>be</em>.
*
* @param element
* the element to be checked
* @return <code>true</code> if the element is copular.
*/
public static boolean isCopular(NLGElement element) {
boolean copular = false;
if (element instanceof InflectedWordElement) {
copular = "be".equalsIgnoreCase(((InflectedWordElement) element) //$NON-NLS-1$
.getBaseForm());
} else if (element instanceof WordElement) {
copular = "be".equalsIgnoreCase(((WordElement) element) //$NON-NLS-1$
.getBaseForm());
} else if (element instanceof PhraseElement) {
// get the head and check if it's "be"
NLGElement head = element instanceof SPhraseSpec ? ((SPhraseSpec) element)
.getVerb()
: ((PhraseElement) element).getHead();
if (head != null) {
copular = (head instanceof WordElement && "be"
.equals(((WordElement) head).getBaseForm()));
}
}
return copular;
}
}