content: /**
   * Portions Copyright 2006-2007 DFKI GmbH.
   * Portions Copyright 2001 Sun Microsystems, Inc.
   * Portions Copyright 1999-2001 Language Technologies Institute,
   * Carnegie Mellon University.
   * All Rights Reserved.  Use is subject to license terms.
   * <p/>
   * Permission is hereby granted, free of charge, to use and distribute
   * this software and its documentation without restriction, including
   * without limitation the rights to use, copy, modify, merge, publish,
   * distribute, sublicense, and/or sell copies of this work, and to
   * permit persons to whom this work is furnished to do so, subject to
   * the following conditions:
   * <p/>
   * 1. The code must retain the above copyright notice, this list of
   * conditions and the following disclaimer.
   * 2. Any modifications must be clearly marked as such.
   * 3. Original authors' names are not deleted.
   * 4. The authors' names are not used to endorse or promote products
   * derived from this software without specific prior written
   * permission.
   * <p/>
   * DFKI GMBH AND THE CONTRIBUTORS TO THIS WORK DISCLAIM ALL WARRANTIES WITH
   * REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF
   * MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL DFKI GMBH NOR THE
   * CONTRIBUTORS BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL
   * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
   * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
   * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
   * THIS SOFTWARE.
   */
  
  
  package marytts.features;
  
  
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.HashSet;
  import java.util.Set;
  import java.util.StringTokenizer;
  
  import marytts.datatypes.MaryXML;
  import marytts.unitselection.select.DiphoneTarget;
  import marytts.unitselection.select.HalfPhoneTarget;
  import marytts.unitselection.select.Target;
  import marytts.util.dom.DomUtils;
  import marytts.util.dom.MaryDomUtils;
  import marytts.util.string.ByteStringTranslator;
  import mf.org.w3c.dom.Document;
  import mf.org.w3c.dom.Element;
  import mf.org.w3c.dom.traversal.TreeWalker;
  
  /**
   * A collection of feature processors that operate on Target objects.
   *
   * @author schroed
   */
  public class MaryGenericFeatureProcessors {
      /**
       * flite never returns an int more than 19 from a feature processor,
       * we duplicate that behavior in the processors so that our tests will match.
       * let's keep this number as a constant for better overview
       */
      private static final int RAIL_LIMIT = 19;
      private static final String[] ZERO_TO_NINETEEN = new String[]{"0", "1", "2", "3", "4", "5", "6", "7",
              "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19"};
  
      // no instances
      protected MaryGenericFeatureProcessors() {
      }
  
      /**
       * Navigate from a target to an item.
       * Classes implementing this interface will retrieve
       * meaningful items given the target.
       *
       * @author Marc Schr&ouml;der
       */
      public interface TargetElementNavigator {
          /**
           * Given the target, retrieve an XML Element.
           *
           * @param target
           * @return an item selected according to this navigator,
           * or null if there is no such item.
           */
          Element getElement(Target target);
      }
  
      /**
       * Retrieve the segment belonging to this target.
       *
       * @author Marc Schr&ouml;der
       */
      public static class SegmentNavigator implements TargetElementNavigator {
          @Override
          public Element getElement(Target target) {
              return target.getMaryxmlElement();
          }
      }
  
      /**
       * Retrieve the segment preceding the segment which belongs to this target.
       *
       * @author Marc Schr&ouml;der
       */
      public static class PrevSegmentNavigator implements TargetElementNavigator {
          @Override
          public Element getElement(Target target) {
              Element segment = target.getMaryxmlElement();
              if (segment == null) return null;
              Element sentence = (Element) DomUtils.getAncestor(segment, MaryXML.SENTENCE);
              if (sentence == null) return null;
              TreeWalker tw = DomUtils.createTreeWalker(sentence, MaryXML.PHONE, MaryXML.BOUNDARY);
              tw.setCurrentNode(segment);
              Element previous = (Element) tw.previousNode();
              return previous;
          }
      }
  
      /**
       * Retrieve the segment two before the segment which belongs to this target.
       *
       * @author Marc Schr&ouml;der
       */
      public static class PrevPrevSegmentNavigator implements TargetElementNavigator {
          @Override
          public Element getElement(Target target) {
              Element segment = target.getMaryxmlElement();
              if (segment == null) return null;
              Element sentence = (Element) DomUtils.getAncestor(segment, MaryXML.SENTENCE);
              if (sentence == null) return null;
              TreeWalker tw = DomUtils.createTreeWalker(sentence, MaryXML.PHONE, MaryXML.BOUNDARY);
              tw.setCurrentNode(segment);
              Element previous = (Element) tw.previousNode();
              Element pp = (Element) tw.previousNode();
              return pp;
          }
      }
  
      /**
       * Retrieve the segment following the segment which belongs to this target.
       *
       * @author Marc Schr&ouml;der
       */
      public static class NextSegmentNavigator implements TargetElementNavigator {
          @Override
          public Element getElement(Target target) {
              Element segment = target.getMaryxmlElement();
              if (segment == null) return null;
              Element sentence = (Element) DomUtils.getAncestor(segment, MaryXML.SENTENCE);
              if (sentence == null) return null;
              TreeWalker tw = DomUtils.createTreeWalker(sentence, MaryXML.PHONE, MaryXML.BOUNDARY);
              tw.setCurrentNode(segment);
              Element next = (Element) tw.nextNode();
              return next;
          }
      }
  
      /**
       * Retrieve the segment two after the segment which belongs to this target.
       *
       * @author Marc Schr&ouml;der
       */
      public static class NextNextSegmentNavigator implements TargetElementNavigator {
          @Override
          public Element getElement(Target target) {
              Element segment = target.getMaryxmlElement();
              if (segment == null) return null;
              Element sentence = (Element) DomUtils.getAncestor(segment, MaryXML.SENTENCE);
              if (sentence == null) return null;
              TreeWalker tw = DomUtils.createTreeWalker(sentence, MaryXML.PHONE, MaryXML.BOUNDARY);
              tw.setCurrentNode(segment);
              Element next = (Element) tw.nextNode();
              Element nn = (Element) tw.nextNode();
              return nn;
          }
      }
  
      /**
       * Retrieve the first segment in the word to which this target belongs.
       */
      public static class FirstSegmentInWordNavigator implements TargetElementNavigator {
          @Override
          public Element getElement(Target target) {
              Element segment = target.getMaryxmlElement();
              if (segment == null) return null;
              Element word = (Element) DomUtils.getAncestor(segment, MaryXML.TOKEN);
              if (word == null) return null;
              TreeWalker tw = DomUtils.createTreeWalker(word, MaryXML.PHONE);
              Element first = (Element) tw.firstChild();
              if (first != null) {
                  assert first.getTagName().equals(MaryXML.PHONE) : "Unexpected tag name: expected " + MaryXML.PHONE + ", got " + first.getTagName();
              }
              return first;
          }
      }
  
      /**
       * Retrieve the last segment in the word to which this target belongs.
       */
      public static class LastSegmentInWordNavigator implements TargetElementNavigator {
          @Override
          public Element getElement(Target target) {
              Element segment = target.getMaryxmlElement();
              if (segment == null) return null;
              Element word = (Element) DomUtils.getAncestor(segment, MaryXML.TOKEN);
              if (word == null) return null;
              TreeWalker tw = DomUtils.createTreeWalker(word, MaryXML.PHONE);
              Element last = (Element) tw.lastChild();
              if (last != null) {
                  assert last.getTagName().equals(MaryXML.PHONE) : "Unexpected tag name: expected " + MaryXML.PHONE + ", got " + last.getTagName();
              }
              return last;
          }
      }
  
      /**
       * Retrieve the first syllable in the word to which this target belongs.
       */
      public static class FirstSyllableInWordNavigator implements TargetElementNavigator {
          @Override
          public Element getElement(Target target) {
              Element segment = target.getMaryxmlElement();
              if (segment == null) return null;
              Element word = (Element) DomUtils.getAncestor(segment, MaryXML.TOKEN);
              if (word == null) return null;
              TreeWalker tw = DomUtils.createTreeWalker(word, MaryXML.SYLLABLE);
              Element first = (Element) tw.firstChild();
              if (first != null) {
                  assert first.getTagName().equals(MaryXML.SYLLABLE) : "Unexpected tag name: expected " + MaryXML.SYLLABLE + ", got " + first.getTagName();
              }
              return first;
          }
      }
  
      /**
       * Retrieve the last syllable in the word to which this target belongs.
       */
      public static class LastSyllableInWordNavigator implements TargetElementNavigator {
          @Override
          public Element getElement(Target target) {
              Element segment = target.getMaryxmlElement();
              if (segment == null) return null;
              Element word = (Element) DomUtils.getAncestor(segment, MaryXML.TOKEN);
              if (word == null) return null;
              TreeWalker tw = DomUtils.createTreeWalker(word, MaryXML.SYLLABLE);
              Element last = (Element) tw.lastChild();
              if (last != null) {
                  assert last.getTagName().equals(MaryXML.SYLLABLE) : "Unexpected tag name: expected " + MaryXML.SYLLABLE + ", got " + last.getTagName();
              }
              return last;
          }
      }
  
      /**
       * Retrieve the syllable belonging to this target.
       *
       * @author Marc Schr&ouml;der
       */
      public static class SyllableNavigator implements TargetElementNavigator {
          @Override
          public Element getElement(Target target) {
              Element segment = target.getMaryxmlElement();
              if (segment == null) return null;
              if (!segment.getTagName().equals(MaryXML.PHONE)) return null;
              Element syllable = (Element) segment.getParentNode();
              if (syllable != null) {
                  assert syllable.getTagName().equals(MaryXML.SYLLABLE) : "Unexpected tag name: expected " + MaryXML.SYLLABLE + ", got " + syllable.getTagName();
              }
              return syllable;
          }
      }
  
      /**
       * Retrieve the syllable before the syllable belonging to this target.
       *
       * @author Marc Schr&ouml;der
       */
      public static class PrevSyllableNavigator implements TargetElementNavigator {
          @Override
          public Element getElement(Target target) {
              Element segment = target.getMaryxmlElement();
              if (segment == null) return null;
              Element current;
              if (segment.getTagName().equals(MaryXML.PHONE)) {
                  Element syllable = (Element) segment.getParentNode();
                  if (syllable == null) return null;
                  current = syllable;
              } else { // boundary
                  current = segment;
              }
              Element sentence = (Element) DomUtils.getAncestor(segment, MaryXML.SENTENCE);
              if (sentence == null) return null;
              TreeWalker tw = DomUtils.createTreeWalker(sentence, MaryXML.SYLLABLE);
              tw.setCurrentNode(current);
              Element previous = (Element) tw.previousNode();
              if (previous != null) {
                  assert previous.getTagName().equals(MaryXML.SYLLABLE) : "Unexpected tag name: expected " + MaryXML.SYLLABLE + ", got " + previous.getTagName();
              }
              return previous;
          }
      }
  
      /**
       * Retrieve the syllable two before the syllable belonging to this target.
       *
       * @author Marc Schr&ouml;der
       */
      public static class PrevPrevSyllableNavigator implements TargetElementNavigator {
          @Override
          public Element getElement(Target target) {
              Element segment = target.getMaryxmlElement();
              if (segment == null) return null;
              Element current;
              if (segment.getTagName().equals(MaryXML.PHONE)) {
                  Element syllable = (Element) segment.getParentNode();
                  if (syllable == null) return null;
                  current = syllable;
              } else { // boundary
                  current = segment;
              }
              Element sentence = (Element) DomUtils.getAncestor(segment, MaryXML.SENTENCE);
              if (sentence == null) return null;
              TreeWalker tw = DomUtils.createTreeWalker(sentence, MaryXML.SYLLABLE);
              tw.setCurrentNode(current);
              Element previous = (Element) tw.previousNode();
              Element pp = (Element) tw.previousNode();
              if (pp != null) {
                  assert pp.getTagName().equals(MaryXML.SYLLABLE) : "Unexpected tag name: expected " + MaryXML.SYLLABLE + ", got " + pp.getTagName();
              }
              return pp;
          }
      }
  
      /**
       * Retrieve the syllable following the syllable belonging to this target.
       *
       * @author Marc Schr&ouml;der
       */
      public static class NextSyllableNavigator implements TargetElementNavigator {
          @Override
          public Element getElement(Target target) {
              Element segment = target.getMaryxmlElement();
              if (segment == null) return null;
              Element current;
              if (segment.getTagName().equals(MaryXML.PHONE)) {
                  Element syllable = (Element) segment.getParentNode();
                  if (syllable == null) return null;
                  current = syllable;
              } else { // boundary
                  current = segment;
              }
              Element sentence = (Element) DomUtils.getAncestor(segment, MaryXML.SENTENCE);
              if (sentence == null) return null;
              TreeWalker tw = DomUtils.createTreeWalker(sentence, MaryXML.SYLLABLE);
              tw.setCurrentNode(current);
              Element next = (Element) tw.nextNode();
              if (next != null) {
                  assert next.getTagName().equals(MaryXML.SYLLABLE) : "Unexpected tag name: expected " + MaryXML.SYLLABLE + ", got " + next.getTagName();
              }
              return next;
          }
      }
  
      /**
       * Retrieve the syllable two after the syllable belonging to this target.
       *
       * @author Marc Schr&ouml;der
       */
      public static class NextNextSyllableNavigator implements TargetElementNavigator {
          @Override
          public Element getElement(Target target) {
              Element segment = target.getMaryxmlElement();
              if (segment == null) return null;
              Element current;
              if (segment.getTagName().equals(MaryXML.PHONE)) {
                  Element syllable = (Element) segment.getParentNode();
                  if (syllable == null) return null;
                  current = syllable;
              } else { // boundary
                  current = segment;
              }
              Element sentence = (Element) DomUtils.getAncestor(segment, MaryXML.SENTENCE);
              if (sentence == null) return null;
              TreeWalker tw = DomUtils.createTreeWalker(sentence, MaryXML.SYLLABLE);
              tw.setCurrentNode(current);
              Element next = (Element) tw.nextNode();
              Element nn = (Element) tw.nextNode();
              if (nn != null) {
                  assert nn.getTagName().equals(MaryXML.SYLLABLE) : "Unexpected tag name: expected " + MaryXML.SYLLABLE + ", got " + nn.getTagName();
              }
              return nn;
          }
      }
  
      /**
       * Retrieve the word belonging to this target.
       *
       * @author Marc Schr&ouml;der
       */
      public static class WordNavigator implements TargetElementNavigator {
          @Override
          public Element getElement(Target target) {
              Element segment = target.getMaryxmlElement();
              if (segment == null) return null;
              Element word = (Element) DomUtils.getAncestor(segment, MaryXML.TOKEN);
              if (word != null) {
                  assert word.getTagName().equals(MaryXML.TOKEN) : "Unexpected tag name: expected " + MaryXML.TOKEN + ", got " + word.getTagName();
              }
              return word;
          }
      }
  
      /**
       * Last syllable in phrase.
       */
      public static class LastSyllableInPhraseNavigator implements TargetElementNavigator {
          @Override
          public Element getElement(Target target) {
              Element segment = target.getMaryxmlElement();
              if (segment == null) return null;
              Element phrase = (Element) DomUtils.getAncestor(segment, MaryXML.PHRASE);
              if (phrase == null) return null;
              TreeWalker tw = DomUtils.createTreeWalker(phrase, MaryXML.SYLLABLE);
              Element last = (Element) tw.lastChild();
              if (last != null) {
                  assert last.getTagName().equals(MaryXML.SYLLABLE) : "Unexpected tag name: expected " + MaryXML.SYLLABLE + ", got " + last.getTagName();
              }
              return last;
          }
      }
  
      public static class NextWordNavigator implements TargetElementNavigator {
          @Override
          public Element getElement(Target target) {
              Element segment = target.getMaryxmlElement();
              if (segment == null) return null;
              Element current;
              if (segment.getTagName().equals(MaryXML.PHONE)) {
                  Element word = (Element) DomUtils.getAncestor(segment, MaryXML.TOKEN);
                  if (word == null) return null;
                  current = word;
              } else { // boundary
                  current = segment;
              }
              Element sentence = (Element) DomUtils.getAncestor(segment, MaryXML.SENTENCE);
              if (sentence == null) return null;
              TreeWalker tw = DomUtils.createTreeWalker(sentence, MaryXML.TOKEN);
              tw.setCurrentNode(current);
              // The next word is the next token with a "ph" attribute:
              Element nextWord = null;
              Element nextToken;
              while ((nextToken = (Element) tw.nextNode()) != null) {
                  if (nextToken.hasAttribute("ph")) {
                      nextWord = nextToken;
                      break;
                  }
              }
              if (nextWord != null) {
                  assert nextWord.getTagName().equals(MaryXML.TOKEN) : "Unexpected tag name: expected " + MaryXML.TOKEN + ", got " + nextWord.getTagName();
              }
              return nextWord;
          }
      }
  
      public static class PrevWordNavigator implements TargetElementNavigator {
          @Override
          public Element getElement(Target target) {
              Element segment = target.getMaryxmlElement();
              if (segment == null) return null;
              Element current;
              if (segment.getTagName().equals(MaryXML.PHONE)) {
                  Element word = (Element) DomUtils.getAncestor(segment, MaryXML.TOKEN);
                  if (word == null) return null;
                  current = word;
              } else { // boundary
                  current = segment;
              }
              Element sentence = (Element) DomUtils.getAncestor(segment, MaryXML.SENTENCE);
              if (sentence == null) return null;
              TreeWalker tw = DomUtils.createTreeWalker(sentence, MaryXML.TOKEN);
              tw.setCurrentNode(current);
              // The next word is the next token with a "ph" attribute:
              Element prevWord = null;
              Element prevToken;
              while ((prevToken = (Element) tw.previousNode()) != null) {
                  if (prevToken.hasAttribute("ph")) {
                      prevWord = prevToken;
                      break;
                  }
              }
              if (prevWord != null) {
                  assert prevWord.getTagName().equals(MaryXML.TOKEN) : "Unexpected tag name: expected " + MaryXML.TOKEN + ", got " + prevWord.getTagName();
              }
              return prevWord;
          }
      }
  
      public static class FirstSegmentNextWordNavigator implements TargetElementNavigator {
          @Override
          public Element getElement(Target target) {
              Element segment = target.getMaryxmlElement();
              if (segment == null) return null;
              Element current;
              if (segment.getTagName().equals(MaryXML.PHONE)) {
                  Element word = (Element) DomUtils.getAncestor(segment, MaryXML.TOKEN);
                  if (word == null) return null;
                  current = word;
              } else { // boundary
                  current = segment;
              }
              Element sentence = (Element) DomUtils.getAncestor(segment, MaryXML.SENTENCE);
              if (sentence == null) return null;
              TreeWalker tw = DomUtils.createTreeWalker(sentence, MaryXML.TOKEN);
              tw.setCurrentNode(current);
              // The next word is the next token with a "ph" attribute:
              Element nextWord = null;
              Element nextToken;
              while ((nextToken = (Element) tw.nextNode()) != null) {
                  if (nextToken.hasAttribute("ph")) {
                      nextWord = nextToken;
                      break;
                  }
              }
              if (nextWord == null) {
                  return null;
              }
              assert nextWord.getTagName().equals(MaryXML.TOKEN) : "Unexpected tag name: expected " + MaryXML.TOKEN + ", got " + nextWord.getTagName();
              TreeWalker sw = DomUtils.createTreeWalker(nextWord, MaryXML.PHONE);
              Element first = (Element) sw.firstChild();
              if (first != null) {
                  assert first.getTagName().equals(MaryXML.PHONE) : "Unexpected tag name: expected " + MaryXML.PHONE + ", got " + first.getTagName();
              }
              return first;
          }
      }
  
      public static class LastWordInSentenceNavigator implements TargetElementNavigator {
          @Override
          public Element getElement(Target target) {
              Element segment = target.getMaryxmlElement();
              if (segment == null) return null;
              Element sentence = (Element) DomUtils.getAncestor(segment, MaryXML.SENTENCE);
              if (sentence == null) return null;
              TreeWalker tw = DomUtils.createTreeWalker(sentence, MaryXML.TOKEN);
              Element lastWord = null;
              Element lastToken = (Element) tw.lastChild();
              // The last word is the lastToken which has a "ph" attribute:
              while (lastToken != null) {
                  if (lastToken.hasAttribute("ph")) {
                      lastWord = lastToken;
                      break;
                  }
                  lastToken = (Element) tw.previousNode();
              }
  
              if (lastWord != null) {
                  assert lastWord.getTagName().equals(MaryXML.TOKEN) : "Unexpected tag name: expected " + MaryXML.TOKEN + ", got " + lastWord.getTagName();
              }
              return lastWord;
          }
      }
  
      /**
       * Indicate whether a unit is an edge unit, which is never the case for a target.
       */
      public static class Edge implements ByteValuedFeatureProcessor {
          @Override
          public String getName() {
              return "edge";
          }
  
          @Override
          public String[] getValues() {
              return new String[]{"0", "start", "end"};
          }
  
          /**
           * This processor always returns 0 for targets.
           */
          @Override
          public byte process(Target target) {
              return (byte) 0;
          }
  
      }
  
      /**
       * Is the given half phone target a left or a right half?
       *
       * @author Marc Schr&ouml;der
       */
      public static class HalfPhoneLeftRight implements ByteValuedFeatureProcessor {
          protected ByteStringTranslator values;
  
          /**
           * Initialise a HalfPhoneLeftRight feature processor.
           */
          public HalfPhoneLeftRight() {
              this.values = new ByteStringTranslator(new String[]{
                      "0", "L", "R"
              });
          }
  
          @Override
          public String getName() {
              return "halfphone_lr";
          }
  
          @Override
          public String[] getValues() {
              return values.getStringValues();
          }
  
          @Override
          public byte process(Target target) {
              if (!(target instanceof HalfPhoneTarget))
                  return 0;
              HalfPhoneTarget hpTarget = (HalfPhoneTarget) target;
              String value = (hpTarget.isLeftHalf() ? "L" : "R");
              return values.get(value);
          }
      }
  
      /**
       * Sentence Style for the given target
       *
       * @author Sathish Chandra Pammi
       */
  
      public static class Style implements ByteValuedFeatureProcessor {
          protected final String styleTagName = "style";
          protected ByteStringTranslator values;
          protected TargetElementNavigator navigator;
  
          /**
           * Initialize a speaking Style feature processor.
           */
          public Style() {
              this.values = new ByteStringTranslator(new String[]{
                      "0", "neutral", "poker", "happy", "sad", "angry", "excited"
              });
              this.navigator = new SegmentNavigator();
          }
  
          @Override
          public String getName() {
              return styleTagName;
          }
  
          @Override
          public String[] getValues() {
              return values.getStringValues();
          }
  
          @Override
          public byte process(Target target) {
              String style = null;
              Element segment = target.getMaryxmlElement();
              if (segment != null) {
                  Element prosody = DomUtils.getClosestAncestorWithAttribute(segment, MaryXML.PROSODY, styleTagName);
  
                  if (prosody != null) {
                      style = prosody.getAttribute(styleTagName);
                  }
              }
              if (style == null || style.equals("")) style = "0";
              if (values.contains(style)) {
                  return values.get(style);
              } else { // silently ignore unknown values
                  return 0;
              }
          }
      }
  
      /**
       * Checks to see if the given syllable is accented.
       */
      public static class Accented implements ByteValuedFeatureProcessor {
          protected String name;
          protected TargetElementNavigator navigator;
  
          public Accented(String name, TargetElementNavigator syllableNavigator) {
              this.name = name;
              this.navigator = syllableNavigator;
          }
  
          @Override
          public String getName() {
              return name;
          }
  
          @Override
          public String[] getValues() {
              return new String[]{"0", "1"};
          }
  
          /**
           * Performs some processing on the given item.
           *
           * @param target the target to process
           * @return "1" if the syllable is accented; otherwise "0"
           */
          @Override
          public byte process(Target target) {
              Element syllable = navigator.getElement(target);
              if (syllable != null && syllable.hasAttribute("accent")) {
                  return (byte) 1;
              } else {
                  return (byte) 0;
              }
          }
      }
  
      /**
       * Checks to see if the given syllable is stressed.
       */
      public static class Stressed implements ByteValuedFeatureProcessor {
          protected String name;
          protected TargetElementNavigator navigator;
  
          public Stressed(String name, TargetElementNavigator syllableNavigator) {
              this.name = name;
              this.navigator = syllableNavigator;
          }
  
          @Override
          public String getName() {
              return name;
          }
  
          @Override
          public String[] getValues() {
              return new String[]{"0", "1"};
          }
  
          /**
           * Performs some processing on the given item.
           *
           * @param target the target to process
           * @return "1" if the syllable is stressed; otherwise "0"
           */
          @Override
          public byte process(Target target) {
              Element syllable = navigator.getElement(target);
              if (syllable == null) return 0;
              String value = syllable.getAttribute("stress");
              if (value.equals("")) return 0;
              byte stressValue = Byte.parseByte(value);
              if (stressValue > 1) {
                  //out of range, set to 1
                  stressValue = 1;
              }
              return stressValue;
          }
      }
  
      /**
       * Syllable tone for the given target
       *
       * @author sathish pammi
       */
      public static class SyllableTone implements ByteValuedFeatureProcessor {
          protected String name;
          protected TargetElementNavigator navigator;
  
          public SyllableTone(String name, TargetElementNavigator syllableNavigator) {
              this.name = name;
              this.navigator = syllableNavigator;
          }
  
          @Override
          public String getName() {
              return name;
          }
  
          @Override
          public String[] getValues() {
              return new String[]{"0", "1", "2", "3", "4"};
          }
  
          /**
           * Performs some processing on the given item.
           *
           * @param target the target to process
           * @return tone value
           */
          @Override
          public byte process(Target target) {
              Element syllable = navigator.getElement(target);
              if (syllable == null) return 0;
              String value = syllable.getAttribute("tone");
              if (value.equals("")) return 0;
              byte toneValue = Byte.parseByte(value);
              if (toneValue > 4 || toneValue < 1) {
                  //out of range, set to 0
                  toneValue = 0;
              }
              return toneValue;
          }
      }
  
      /**
       * Returns as a byte the number of phrases in the current sentence.
       */
      public static class SentenceNumPhrases implements ByteValuedFeatureProcessor {
          public SentenceNumPhrases() {
          }
  
          @Override
          public String getName() {
              return "sentence_numphrases";
          }
  
          @Override
          public String[] getValues() {
              return ZERO_TO_NINETEEN;
          }
  
          /**
           * @return the number of phrases in the sentence
           */
          @Override
          public byte process(Target target) {
              Element segment = target.getMaryxmlElement();
              if (segment == null) return (byte) 0;
              Element sentence = (Element) DomUtils.getAncestor(segment, MaryXML.SENTENCE);
              if (sentence == null) return (byte) 0;
              TreeWalker tw = DomUtils.createTreeWalker(sentence, MaryXML.PHRASE);
              int count = 0;
              Element e;
              while ((e = (Element) tw.nextNode()) != null && count < RAIL_LIMIT) {
                  count++;
              }
              return (byte) count;
          }
      }
  
      /**
       * Returns as an Integer the number of words in the current sentence. This is
       * a feature processor. A feature processor takes an item, performs some
       * sort of processing on the item and returns an object.
       */
      public static class SentenceNumWords implements ByteValuedFeatureProcessor {
          public SentenceNumWords() {
          }
  
          @Override
          public String getName() {
              return "sentence_numwords";
          }
  
          @Override
          public String[] getValues() {
              return ZERO_TO_NINETEEN;
          }
  
          /**
           * @return the number of words in the sentence
           */
          @Override
          public byte process(Target target) {
              Element segment = target.getMaryxmlElement();
              if (segment == null) return (byte) 0;
              Element sentence = (Element) DomUtils.getAncestor(segment, MaryXML.SENTENCE);
              if (sentence == null) return (byte) 0;
              TreeWalker tw = DomUtils.createTreeWalker(sentence, MaryXML.TOKEN);
              int count = 0;
              Element e;
              while ((e = (Element) tw.nextNode()) != null && count < RAIL_LIMIT) {
                  // only tokens with a "ph" attribute count as words:
                  if (e.hasAttribute("ph"))
                      count++;
              }
              return (byte) count;
          }
      }
  
      /**
       * Returns as a byte the number of phrases in the current sentence.
       */
      public static class PhraseNumSyls implements ByteValuedFeatureProcessor {
          public PhraseNumSyls() {
          }
  
          @Override
          public String getName() {
              return "phrase_numsyls";
          }
  
          @Override
          public String[] getValues() {
              return ZERO_TO_NINETEEN;
          }
  
          /**
           * @return the number of words in the phrase
           */
          @Override
          public byte process(Target target) {
              Element segment = target.getMaryxmlElement();
              if (segment == null) return (byte) 0;
              Element phrase = (Element) DomUtils.getAncestor(segment, MaryXML.PHRASE);
              if (phrase == null) return (byte) 0;
              TreeWalker tw = DomUtils.createTreeWalker(phrase, MaryXML.SYLLABLE);
              int count = 0;
              Element e;
              while ((e = (Element) tw.nextNode()) != null && count < RAIL_LIMIT) {
                  count++;
              }
              return (byte) count;
          }
      }
  
      /**
       * Returns as a byte the number of words in the current phrase.
       */
      public static class PhraseNumWords implements ByteValuedFeatureProcessor {
          public PhraseNumWords() {
          }
  
          @Override
          public String getName() {
              return "phrase_numwords";
          }
  
          @Override
          public String[] getValues() {
              return ZERO_TO_NINETEEN;
          }
  
          /**
           * Performs some processing on the given item.
           *
           * @param item the item to process
           * @return the number of words in the phrase
           */
          @Override
          public byte process(Target target) {
              Element segment = target.getMaryxmlElement();
              if (segment == null) return (byte) 0;
              Element phrase = (Element) DomUtils.getAncestor(segment, MaryXML.PHRASE);
              if (phrase == null) return (byte) 0;
              TreeWalker tw = DomUtils.createTreeWalker(phrase, MaryXML.TOKEN);
              int count = 0;
              Element e;
              while ((e = (Element) tw.nextNode()) != null && count < RAIL_LIMIT) {
                  count++;
              }
              return (byte) count;
          }
      }
  
  
      /**
       * Returns as an Integer the number of syllables in the given word. This is
       * a feature processor. A feature processor takes an item, performs some
       * sort of processing on the item and returns an object.
       */
      public static class WordNumSyls implements ByteValuedFeatureProcessor {
          public WordNumSyls() {
          }
  
          @Override
          public String getName() {
              return "word_numsyls";
          }
  
          @Override
          public String[] getValues() {
              return ZERO_TO_NINETEEN;
          }
  
          /**
           * @return the number of syllables in the given word
           */
          @Override
          public byte process(Target target) {
              Element segment = target.getMaryxmlElement();
              if (segment == null) return (byte) 0;
              Element word = (Element) DomUtils.getAncestor(segment, MaryXML.TOKEN);
              if (word == null) return (byte) 0;
              TreeWalker tw = DomUtils.createTreeWalker(word, MaryXML.SYLLABLE);
              int count = 0;
              Element e;
              while ((e = (Element) tw.nextNode()) != null && count < RAIL_LIMIT) {
                  count++;
              }
              return (byte) count;
          }
      }
  
      /**
       * Returns as a byte the number of segments in the given word.
       */
      public static class WordNumSegs implements ByteValuedFeatureProcessor {
          public WordNumSegs() {
          }
  
          @Override
          public String getName() {
              return "word_numsegs";
          }
  
          @Override
          public String[] getValues() {
              return ZERO_TO_NINETEEN;
          }
  
          /**
           * @return the number of segments in the given word
           */
          @Override
          public byte process(Target target) {
              Element segment = target.getMaryxmlElement();
              if (segment == null) return (byte) 0;
              Element word = (Element) DomUtils.getAncestor(segment, MaryXML.TOKEN);
              if (word == null) return (byte) 0;
              TreeWalker tw = DomUtils.createTreeWalker(word, MaryXML.PHONE);
              int count = 0;
              Element e;
              while ((e = (Element) tw.nextNode()) != null && count < RAIL_LIMIT) {
                  count++;
              }
              return (byte) count;
          }
      }
  
      /**
       * Returns as an Integer the number of segments in the current syllable. This is
       * a feature processor. A feature processor takes an item, performs some
       * sort of processing on the item and returns an object.
       */
      public static class SylNumSegs implements ByteValuedFeatureProcessor {
          public SylNumSegs() {
          }
  
          @Override
          public String getName() {
              return "syl_numsegs";
          }
  
          @Override
          public String[] getValues() {
              return ZERO_TO_NINETEEN;
          }
  
          /**
           * Performs some processing on the given item.
           *
           * @param item the item to process
           * @return the number of segments in the given word
           */
          @Override
          public byte process(Target target) {
              Element segment = target.getMaryxmlElement();
              if (segment == null) return (byte) 0;
              if (!segment.getTagName().equals(MaryXML.PHONE)) return 0;
              Element syllable = (Element) segment.getParentNode();
              if (syllable == null) return (byte) 0;
              TreeWalker tw = DomUtils.createTreeWalker(syllable, MaryXML.PHONE);
              int count = 0;
              Element e;
              while ((e = (Element) tw.nextNode()) != null && count < RAIL_LIMIT) {
                  count++;
              }
              return (byte) count;
          }
      }
  
      /**
       * @deprecated, use SegsFromSylStart instead
       */
      public static class PosInSyl extends SegsFromSylStart {
          public PosInSyl() {
              super();
          }
  
          @Override
          public String getName() {
              return "pos_in_syl";
          }
      }
  
  
      /**
       * Finds the position of the phone in the syllable.
       */
      public static class SegsFromSylStart implements ByteValuedFeatureProcessor {
          public SegsFromSylStart() {
          }
  
          @Override
          public String getName() {
              return "segs_from_syl_start";
          }
  
          @Override
          public String[] getValues() {
              return ZERO_TO_NINETEEN;
          }
  
          /**
           * @param target the target to process
           * @return the position of the phone in the syllable
           */
          @Override
          public byte process(Target target) {
              Element segment = target.getMaryxmlElement();
              if (segment == null) return (byte) 0;
              if (!segment.getTagName().equals(MaryXML.PHONE)) return 0;
              int count = 0;
              Element e = segment;
              while ((e = DomUtils.getPreviousSiblingElement(e)) != null && count < RAIL_LIMIT) {
                  count++;
              }
              return (byte) count;
          }
      }
  
  
      /**
       * Finds the position of the phone from the end of the syllable.
       */
      public static class SegsFromSylEnd implements ByteValuedFeatureProcessor {
          public SegsFromSylEnd() {
          }
  
          @Override
          public String getName() {
              return "segs_from_syl_end";
          }
  
          @Override
          public String[] getValues() {
              return ZERO_TO_NINETEEN;
          }
  
          /**
           * @param target the target to process
           * @return the position of the phone in the syllable
           */
          @Override
          public byte process(Target target) {
              Element segment = target.getMaryxmlElement();
              if (segment == null) return (byte) 0;
              if (!segment.getTagName().equals(MaryXML.PHONE)) return 0;
              int count = 0;
              Element e = segment;
              while ((e = DomUtils.getNextSiblingElement(e)) != null && count < RAIL_LIMIT) {
                  count++;
              }
              return (byte) count;
          }
      }
  
      /**
       * Finds the position of the segment from the start of the word.
       */
      public static class SegsFromWordStart implements ByteValuedFeatureProcessor {
          public SegsFromWordStart() {
          }
  
          @Override
          public String getName() {
              return "segs_from_word_start";
          }
  
          @Override
          public String[] getValues() {
              return ZERO_TO_NINETEEN;
          }
  
          /**
           * @param target the target to process
           * @return the position of the phone in the syllable
           */
          @Override
          public byte process(Target target) {
              Element segment = target.getMaryxmlElement();
              if (segment == null) return (byte) 0;
              Element word = (Element) DomUtils.getAncestor(segment, MaryXML.TOKEN);
              if (word == null) return (byte) 0;
              TreeWalker tw = DomUtils.createTreeWalker(word, MaryXML.PHONE);
              tw.setCurrentNode(segment);
              int count = 0;
              Element e;
              while ((e = (Element) tw.previousNode()) != null && count < RAIL_LIMIT) {
                  count++;
              }
              return (byte) count;
          }
      }
  
      /**
       * Finds the position of the segment from the end of the word.
       */
      public static class SegsFromWordEnd implements ByteValuedFeatureProcessor {
          public SegsFromWordEnd() {
          }
  
          @Override
          public String getName() {
              return "segs_from_word_end";
          }
  
          @Override
          public String[] getValues() {
              return ZERO_TO_NINETEEN;
          }
  
          /**
           * Performs some processing on the given item.
           *
           * @param target the target to process
           * @return the position of the phone in the syllable
           */
          @Override
          public byte process(Target target) {
              Element segment = target.getMaryxmlElement();
              if (segment == null) return (byte) 0;
              Element word = (Element) DomUtils.getAncestor(segment, MaryXML.TOKEN);
              if (word == null) return (byte) 0;
              TreeWalker tw = DomUtils.createTreeWalker(word, MaryXML.PHONE);
              tw.setCurrentNode(segment);
              int count = 0;
              Element e;
              while ((e = (Element) tw.nextNode()) != null && count < RAIL_LIMIT) {
                  count++;
              }
              return (byte) count;
          }
      }
  
      /**
       * Finds the position of the syllable from the start of the word.
       */
      public static class SylsFromWordStart implements ByteValuedFeatureProcessor {
          public SylsFromWordStart() {
          }
  
          @Override
          public String getName() {
              return "syls_from_word_start";
          }
  
          @Override
          public String[] getValues() {
              return ZERO_TO_NINETEEN;
          }
  
          /**
           * @param target the target to process
           * @return the position of the syllable in the word
           */
          @Override
          public byte process(Target target) {
              Element segment = target.getMaryxmlElement();
              if (segment == null) return (byte) 0;
              if (!segment.getTagName().equals(MaryXML.PHONE)) return 0;
              Element syllable = (Element) segment.getParentNode();
              if (syllable == null) return (byte) 0;
              int count = 0;
              Element e = syllable;
              while ((e = DomUtils.getPreviousSiblingElement(e)) != null && count < RAIL_LIMIT) {
                  count++;
              }
              return (byte) count;
          }
      }
  
      /**
       * Finds the position of the syllable from the end of the word.
       */
      public static class SylsFromWordEnd implements ByteValuedFeatureProcessor {
          public SylsFromWordEnd() {
          }
  
          @Override
          public String getName() {
              return "syls_from_word_end";
          }
  
          @Override
          public String[] getValues() {
              return ZERO_TO_NINETEEN;
          }
  
          /**
           * @param target the target to process
           * @return the position of the phone in the syllable
           */
          @Override
          public byte process(Target target) {
              Element segment = target.getMaryxmlElement();
              if (segment == null) return (byte) 0;
              if (!segment.getTagName().equals(MaryXML.PHONE)) return 0;
              Element syllable = (Element) segment.getParentNode();
              if (syllable == null) return (byte) 0;
              int count = 0;
              Element e = syllable;
              while ((e = DomUtils.getNextSiblingElement(e)) != null && count < RAIL_LIMIT) {
                  count++;
              }
              return (byte) count;
          }
      }
  
  
      /**
       * Determines the break level after this syllable.
       */
      public static class SylBreak implements ByteValuedFeatureProcessor {
          protected String name;
          protected TargetElementNavigator navigator;
  
          public SylBreak(String name, TargetElementNavigator syllableNavigator) {
              this.name = name;
              this.navigator = syllableNavigator;
          }
  
          @Override
          public String getName() {
              return name;
          }
  
          /**
           * "4" for a big break, "3" for a break; "1" = word-final; "0" = within-word
           */
          @Override
          public String[] getValues() {
              return new String[]{"0", "1", "unused", "3", "4"};
          }
  
          /**
           * @param target the target to process
           * @return the break level after the syllable returned by syllableNavigator
           */
          @Override
          public byte process(Target target) {
              Element syllable = navigator.getElement(target);
              if (syllable == null) return 0;
              // is there another syllable following in the token?
              if (DomUtils.getNextSiblingElement(syllable) != null) return 0;
              // else, it is at least word-final.
              Element word = (Element) syllable.getParentNode();
              if (word == null) return 0;
              assert word.getTagName().equals(MaryXML.TOKEN) : "Unexpected tag name: expected " + MaryXML.TOKEN + ", got " + word.getTagName();
              Element sentence = (Element) DomUtils.getAncestor(word, MaryXML.SENTENCE);
              if (sentence == null) return 0;
              TreeWalker tw = DomUtils.createTreeWalker(sentence, MaryXML.TOKEN, MaryXML.BOUNDARY);
              tw.setCurrentNode(word);
              // The next word is the next token with a "ph" attribute:
              Element e;
              while ((e = (Element) tw.nextNode()) != null) {
                  if (e.getTagName().equals(MaryXML.BOUNDARY)
                          || e.getTagName().equals(MaryXML.TOKEN) && e.hasAttribute("ph"))
                      break;
              }
              if (e == null) {
                  // we are the last token in the sentence, but there is no boundary...
                  // OK, let's say it is sentence-final anyway:
                  return 4;
              }
              if (e.getTagName().equals(MaryXML.TOKEN)) {
                  // a word follows
                  return 1;
              }
              assert e.getTagName().equals(MaryXML.BOUNDARY) : "Unexpected tag name: expected " + MaryXML.BOUNDARY + ", got " + e.getTagName();
              String bi = e.getAttribute("breakindex");
              if (bi.equals("")) {
                  // no breakindex
                  return 1;
              }
              try {
                  int ibi = Integer.parseInt(bi);
                  if (ibi >= 4) return 4;
                  if (ibi == 3) return 3;
              } catch (NumberFormatException nfe) {
              }
              // default: a word boundary
              return 1;
          }
      }
  
      /**
       * Classifies the the syllable as single, initial, mid or final.
       */
      public static class PositionType implements ByteValuedFeatureProcessor {
          protected TargetElementNavigator navigator;
          protected ByteStringTranslator values;
  
          public PositionType() {
              values = new ByteStringTranslator(new String[]{
                      "0", "single", "final", "initial", "mid"
              });
              navigator = new SyllableNavigator();
          }
  
          @Override
          public String getName() {
              return "position_type";
          }
  
          @Override
          public String[] getValues() {
              return values.getStringValues();
          }
  
          /**
           * @param target the target to process
           * @return classifies the syllable as "single", "final", "initial" or
           * "mid"
           */
          @Override
          public byte process(Target target) {
              Element syllable = navigator.getElement(target);
              if (syllable == null) return 0;
              String type;
              if (DomUtils.getNextSiblingElement(syllable) == null) {
                  if (DomUtils.getPreviousSiblingElement(syllable) == null) {
                      type = "single";
                  } else {
                      type = "final";
                  }
              } else if (DomUtils.getPreviousSiblingElement(syllable) == null) {
                  type = "initial";
              } else {
                  type = "mid";
              }
              return values.get(type);
          }
      }
  
      /**
       * Checks if segment is a pause.
       */
      public static class IsPause implements ByteValuedFeatureProcessor {
          protected TargetElementNavigator navigator;
          protected String name;
  
          public IsPause(String name, TargetElementNavigator segmentNavigator) {
              this.name = name;
              this.navigator = segmentNavigator;
          }
  
          @Override
          public String getName() {
              return name;
          }
  
          @Override
          public String[] getValues() {
              return new String[]{"0", "1"};
          }
  
          /**
           * Check if segment is a pause
           *
           * @param target the target to process
           * @return 0 if false, 1 if true
           */
          @Override
          public byte process(Target target) {
              Element seg = navigator.getElement(target);
              if (seg == null) return 0;
              if (seg.getTagName().equals(MaryXML.BOUNDARY)) return 1;
              return 0;
          }
      }
  
      public static class BreakIndex implements ByteValuedFeatureProcessor {
          protected ByteStringTranslator values;
  
          public BreakIndex() {
              values = new ByteStringTranslator(new String[]{
                      "0", "1", "2", "3", "4", "5", "6"
              });
          }
  
          @Override
          public String getName() {
              return "breakindex";
          }
  
          @Override
          public String[] getValues() {
              return values.getStringValues();
          }
  
          @Override
          public byte process(Target target) {
              Element segment = target.getMaryxmlElement();
              if (segment == null) return 0;
              Element word = (Element) DomUtils.getAncestor(segment, MaryXML.TOKEN);
              if (word == null) return 0;
              // is there another segment following in the token?
              TreeWalker tww = DomUtils.createTreeWalker(word, MaryXML.PHONE);
              tww.setCurrentNode(segment);
              if (tww.nextNode() != null) return 0;
              // else, it is at least word-final.
              Element sentence = (Element) DomUtils.getAncestor(word, MaryXML.SENTENCE);
              if (sentence == null) return 0;
              TreeWalker tw = DomUtils.createTreeWalker(sentence, MaryXML.TOKEN, MaryXML.BOUNDARY);
              tw.setCurrentNode(word);
              // The next word is the next token with a "ph" attribute:
              Element e;
              while ((e = (Element) tw.nextNode()) != null) {
                  if (e.getTagName().equals(MaryXML.BOUNDARY)
                          || e.getTagName().equals(MaryXML.TOKEN) && e.hasAttribute("ph"))
                      break;
              }
              if (e == null) {
                  // we are the last token in the sentence, but there is no boundary...
                  // OK, let's say it is sentence-final anyway:
                  return 4;
              }
              if (e.getTagName().equals(MaryXML.TOKEN)) {
                  // a word follows
                  return 1;
              }
              assert e.getTagName().equals(MaryXML.BOUNDARY) : "Unexpected tag name: expected " + MaryXML.BOUNDARY + ", got " + e.getTagName();
              String bi = e.getAttribute("breakindex");
              if (bi.equals("")) {
                  // no breakindex
                  return 1;
              }
              try {
                  int ibi = Integer.parseInt(bi);
                  if (ibi > 6) ibi = 6;
                  if (ibi < 2) ibi = 2;
                  return (byte) ibi;
              } catch (NumberFormatException nfe) {
              }
              // default: a word boundary
              return 1;
          }
      }
  
  
      /**
       * The ToBI accent of the current syllable.
       */
      public static class TobiAccent implements ByteValuedFeatureProcessor {
          protected String name;
          protected TargetElementNavigator navigator;
          protected ByteStringTranslator values;
  
          public TobiAccent(String name, TargetElementNavigator syllableNavigator) {
              this.name = name;
              this.navigator = syllableNavigator;
              this.values = new ByteStringTranslator(new String[]{
                      "0", "*", "H*", "!H*", "^H*", "L*", "L+H*", "L*+H", "L+!H*",
                      "L*+!H", "L+^H*", "L*+^H", "H+L*", "H+!H*", "H+^H*",
                      "!H+!H*", "^H+!H*", "^H+^H*", "H*+L", "!H*+L"
              });
          }
  
          @Override
          public String getName() {
              return name;
          }
  
          @Override
          public String[] getValues() {
              return values.getStringValues();
          }
  
          /**
           * For the given syllable item, return its tobi accent,
           * or 0 if there is none.
           */
          @Override
          public byte process(Target target) {
              Element syllable = navigator.getElement(target);
              if (syllable == null) return 0;
              String accent = syllable.getAttribute("accent");
              if (accent.equals("")) {
                  return 0;
              }
              return values.get(accent);
          }
      }
  
      /**
       * The ToBI endtone associated with the current syllable.
       */
      public static class TobiEndtone implements ByteValuedFeatureProcessor {
          protected String name;
          protected TargetElementNavigator navigator;
          protected ByteStringTranslator values;
  
          public TobiEndtone(String name, TargetElementNavigator syllableNavigator) {
              this.name = name;
              this.navigator = syllableNavigator;
              this.values = new ByteStringTranslator(new String[]{
                      "0", "H-", "!H-", "L-", "H-%", "!H-%", "H-^H%",
                      "!H-^H%", "L-H%", "L-%", "L-L%", "H-H%", "H-L%"
              });
          }
  
          @Override
          public String getName() {
              return name;
          }
  
          @Override
          public String[] getValues() {
              return values.getStringValues();
          }
  
          /**
           * For the given syllable item, return its tobi end tone,
           * or 0 if there is none.
           */
          @Override
          public byte process(Target target) {
              Element syllable = navigator.getElement(target);
              if (syllable == null) return 0;
              Element sentence = (Element) DomUtils.getAncestor(syllable, MaryXML.SENTENCE);
              if (sentence == null) return 0;
              TreeWalker tw = DomUtils.createTreeWalker(sentence, MaryXML.SYLLABLE, MaryXML.BOUNDARY);
              tw.setCurrentNode(syllable);
              Element e = (Element) tw.nextNode();
              if (e == null) return 0;
              if (e.getTagName().equals(MaryXML.SYLLABLE)) return 0;
              assert e.getTagName().equals(MaryXML.BOUNDARY) : "Unexpected tag name: expected " + MaryXML.BOUNDARY + ", got " + e.getTagName();
              String endtone = e.getAttribute("tone");
              if (endtone.equals("")) {
                  return 0;
              }
              return values.get(endtone);
          }
      }
  
      /**
       * The next ToBI accent following the current syllable in the current phrase.
       */
      public static class NextAccent extends TobiAccent {
          public NextAccent() {
              super("next_accent", null);
          }
  
          /**
           * Search for an accented syllable, and return its tobi accent,
           * or 0 if there is none.
           */
          @Override
          public byte process(Target target) {
              Element segment = target.getMaryxmlElement();
              if (segment == null) return 0;
              Element current;
              if (segment.getTagName().equals(MaryXML.PHONE)) {
                  Element syllable = (Element) segment.getParentNode();
                  if (syllable == null) return 0;
                  current = syllable;
              } else { // boundary
                  current = segment;
              }
              Element phrase = (Element) DomUtils.getAncestor(current, MaryXML.PHRASE);
              if (phrase == null) return 0;
              TreeWalker tw = DomUtils.createTreeWalker(phrase, MaryXML.SYLLABLE);
              tw.setCurrentNode(current);
              Element s;
              while ((s = (Element) tw.nextNode()) != null) {
                  if (s.hasAttribute("accent")) {
                      String accent = s.getAttribute("accent");
                      return values.get(accent);
                  }
              }
              return 0;
          }
      }
  
      /**
       * The previous ToBI accent preceding the current syllable in the current phrase.
       */
      public static class PrevAccent extends TobiAccent {
          public PrevAccent() {
              super("prev_accent", null);
          }
  
          /**
           * Search for an accented syllable, and return its tobi accent,
           * or 0 if there is none.
           */
          @Override
          public byte process(Target target) {
              Element segment = target.getMaryxmlElement();
              if (segment == null) return 0;
              Element current;
              if (segment.getTagName().equals(MaryXML.PHONE)) {
                  Element syllable = (Element) segment.getParentNode();
                  if (syllable == null) return 0;
                  current = syllable;
              } else { // boundary
                  current = segment;
              }
              Element phrase = (Element) DomUtils.getAncestor(current, MaryXML.PHRASE);
              if (phrase == null) return 0;
              TreeWalker tw = DomUtils.createTreeWalker(phrase, MaryXML.SYLLABLE);
              tw.setCurrentNode(current);
              Element s;
              while ((s = (Element) tw.previousNode()) != null) {
                  if (s.hasAttribute("accent")) {
                      String accent = s.getAttribute("accent");
                      return values.get(accent);
                  }
              }
              return 0;
          }
      }
  
  
      /**
       * The ToBI endtone associated with the last syllable of the current phrase.
       */
      public static class PhraseEndtone extends TobiEndtone {
          public PhraseEndtone() {
              super("phrase_endtone", new LastSyllableInPhraseNavigator());
          }
      }
  
      /**
       * The ToBI endtone associated with the last syllable of the previous phrase.
       */
      public static class PrevPhraseEndtone extends TobiEndtone {
          public PrevPhraseEndtone() {
              super("prev_phrase_endtone", null);
          }
  
          @Override
          public byte process(Target target) {
              Element segment = target.getMaryxmlElement();
              if (segment == null) return 0;
              Element phrase = (Element) DomUtils.getAncestor(segment, MaryXML.PHRASE);
              if (phrase == null) return 0;
              Document doc = phrase.getOwnerDocument();
              TreeWalker tw = DomUtils.createTreeWalker(doc, doc, MaryXML.BOUNDARY);
              tw.setCurrentNode(phrase);
              Element boundary = (Element) tw.previousNode();
              if (boundary == null) return 0;
              String endtone = boundary.getAttribute("tone");
              if (endtone.equals("")) {
                  return 0;
              }
              return values.get(endtone);
          }
      }
  
  
      /**
       * Counts the number of syllables since the start of the phrase.
       */
      public static class SylsFromPhraseStart implements ByteValuedFeatureProcessor {
          public SylsFromPhraseStart() {
          }
  
          @Override
          public String getName() {
              return "syls_from_phrase_start";
          }
  
          @Override
          public String[] getValues() {
              return ZERO_TO_NINETEEN;
          }
  
          /**
           * @param target the target to process
           * @return the number of syllables since the last major break
           */
          @Override
          public byte process(Target target) {
              Element segment = target.getMaryxmlElement();
              if (segment == null) return 0;
              Element phrase = (Element) DomUtils.getAncestor(segment, MaryXML.PHRASE);
              if (phrase == null) return 0;
              int count = 0;
              TreeWalker tw = DomUtils.createTreeWalker(phrase, MaryXML.SYLLABLE);
              Element syllable = (Element) DomUtils.getAncestor(segment, MaryXML.SYLLABLE);
              if (syllable != null) {
                  tw.setCurrentNode(syllable);
              } else {
                  tw.setCurrentNode(segment);
              }
              Element e;
              while ((e = (Element) tw.previousNode()) != null && count < RAIL_LIMIT) {
                  count++;
              }
              return (byte) count;
          }
      }
  
      /**
       * Counts the number of syllables until the end of the phrase.
       */
      public static class SylsFromPhraseEnd implements ByteValuedFeatureProcessor {
          public SylsFromPhraseEnd() {
          }
  
          @Override
          public String getName() {
              return "syls_from_phrase_end";
          }
  
          @Override
          public String[] getValues() {
              return ZERO_TO_NINETEEN;
          }
  
          /**
           * @param target the target to process
           * @return the number of accented syllables since the last major break
           */
          @Override
          public byte process(Target target) {
              Element segment = target.getMaryxmlElement();
              if (segment == null) return 0;
              Element phrase = (Element) DomUtils.getAncestor(segment, MaryXML.PHRASE);
              if (phrase == null) return 0;
              int count = 0;
              TreeWalker tw = DomUtils.createTreeWalker(phrase, MaryXML.SYLLABLE);
              tw.setCurrentNode(segment);
              Element e;
              while ((e = (Element) tw.nextNode()) != null && count < RAIL_LIMIT) {
                  count++;
              }
              return (byte) count;
          }
      }
  
      /**
       * Counts the number of stressed syllables since the start of the phrase.
       */
      public static class StressedSylsFromPhraseStart implements ByteValuedFeatureProcessor {
          public StressedSylsFromPhraseStart() {
          }
  
          @Override
          public String getName() {
              return "stressed_syls_from_phrase_start";
          }
  
          @Override
          public String[] getValues() {
              return ZERO_TO_NINETEEN;
          }
  
          /**
           * @param target the target to process
           * @return the number of stressed syllables since the last major break
           */
          @Override
          public byte process(Target target) {
              Element segment = target.getMaryxmlElement();
              if (segment == null) return 0;
              Element phrase = (Element) DomUtils.getAncestor(segment, MaryXML.PHRASE);
              if (phrase == null) return 0;
              int count = 0;
              TreeWalker tw = DomUtils.createTreeWalker(phrase, MaryXML.SYLLABLE);
              Element syllable = (Element) DomUtils.getAncestor(segment, MaryXML.SYLLABLE);
              if (syllable != null) {
                  tw.setCurrentNode(syllable);
              } else {
                  tw.setCurrentNode(segment);
              }
              Element e;
              while ((e = (Element) tw.previousNode()) != null && count < RAIL_LIMIT) {
                  String stress = e.getAttribute("stress");
                  if (stress.equals("1"))
                      count++;
              }
              return (byte) count;
          }
      }
  
      /**
       * Counts the number of stressed syllables until the end of the phrase.
       */
      public static class StressedSylsFromPhraseEnd implements ByteValuedFeatureProcessor {
          public StressedSylsFromPhraseEnd() {
          }
  
          @Override
          public String getName() {
              return "stressed_syls_from_phrase_end";
          }
  
          @Override
          public String[] getValues() {
              return ZERO_TO_NINETEEN;
          }
  
          /**
           * @param target the target to process
           * @return the number of stressed syllables since the last major break
           */
          @Override
          public byte process(Target target) {
              Element segment = target.getMaryxmlElement();
              if (segment == null) return 0;
              Element phrase = (Element) DomUtils.getAncestor(segment, MaryXML.PHRASE);
              if (phrase == null) return 0;
              int count = 0;
              TreeWalker tw = DomUtils.createTreeWalker(phrase, MaryXML.SYLLABLE);
              tw.setCurrentNode(segment);
              Element e;
              while ((e = (Element) tw.nextNode()) != null && count < RAIL_LIMIT) {
                  String stress = e.getAttribute("stress");
                  if (stress.equals("1"))
                      count++;
              }
              return (byte) count;
          }
      }
  
      /**
       * Counts the number of accented syllables since the start of the phrase.
       */
      public static class AccentedSylsFromPhraseStart implements ByteValuedFeatureProcessor {
          public AccentedSylsFromPhraseStart() {
          }
  
          @Override
          public String getName() {
              return "accented_syls_from_phrase_start";
          }
  
          @Override
          public String[] getValues() {
              return ZERO_TO_NINETEEN;
          }
  
          /**
           * @param target the target to process
           * @return the number of accented syllables since the last major break
           */
          @Override
          public byte process(Target target) {
              Element segment = target.getMaryxmlElement();
              if (segment == null) return 0;
              Element phrase = (Element) DomUtils.getAncestor(segment, MaryXML.PHRASE);
              if (phrase == null) return 0;
              int count = 0;
              TreeWalker tw = DomUtils.createTreeWalker(phrase, MaryXML.SYLLABLE);
              Element syllable = (Element) DomUtils.getAncestor(segment, MaryXML.SYLLABLE);
              if (syllable != null) {
                  tw.setCurrentNode(syllable);
              } else {
                  tw.setCurrentNode(segment);
              }
              Element e;
              while ((e = (Element) tw.previousNode()) != null && count < RAIL_LIMIT) {
                  String accent = e.getAttribute("accent");
                  if (!accent.equals(""))
                      count++;
              }
              return (byte) count;
          }
      }
  
  
      /**
       * Counts the number of accented syllables until the end of the phrase.
       */
      public static class AccentedSylsFromPhraseEnd implements ByteValuedFeatureProcessor {
          public AccentedSylsFromPhraseEnd() {
          }
  
          @Override
          public String getName() {
              return "accented_syls_from_phrase_end";
          }
  
          @Override
          public String[] getValues() {
              return ZERO_TO_NINETEEN;
          }
  
          /**
           * @param target the target to process
           * @return the number of accented syllables since the last major break
           */
          @Override
          public byte process(Target target) {
              Element segment = target.getMaryxmlElement();
              if (segment == null) return 0;
              Element phrase = (Element) DomUtils.getAncestor(segment, MaryXML.PHRASE);
              if (phrase == null) return 0;
              int count = 0;
              TreeWalker tw = DomUtils.createTreeWalker(phrase, MaryXML.SYLLABLE);
              tw.setCurrentNode(segment);
              Element e;
              while ((e = (Element) tw.nextNode()) != null && count < RAIL_LIMIT) {
                  String accent = e.getAttribute("accent");
                  if (!accent.equals(""))
                      count++;
              }
              return (byte) count;
          }
      }
  
  
      /**
       * Counts the number of words since the start of the phrase.
       */
      public static class WordsFromPhraseStart implements ByteValuedFeatureProcessor {
          public WordsFromPhraseStart() {
          }
  
          @Override
          public String getName() {
              return "words_from_phrase_start";
          }
  
          @Override
          public String[] getValues() {
              return ZERO_TO_NINETEEN;
          }
  
          /**
           * @param target the target to process
           * @return the number of words since the last major break
           */
          @Override
          public byte process(Target target) {
              Element segment = target.getMaryxmlElement();
              if (segment == null) return 0;
              Element phrase = (Element) DomUtils.getAncestor(segment, MaryXML.PHRASE);
              if (phrase == null) return 0;
              int count = 0;
              TreeWalker tw = DomUtils.createTreeWalker(phrase, MaryXML.TOKEN);
              Element word = (Element) DomUtils.getAncestor(segment, MaryXML.TOKEN);
              if (word != null) {
                  tw.setCurrentNode(word);
              } else {
                  tw.setCurrentNode(segment);
              }
              Element e;
              while ((e = (Element) tw.previousNode()) != null && count < RAIL_LIMIT) {
                  // only count tokens that have a "ph" attribute:
                  if (e.hasAttribute("ph"))
                      count++;
              }
              return (byte) count;
          }
      }
  
      /**
       * Counts the number of words until the end of the phrase.
       */
      public static class WordsFromPhraseEnd implements ByteValuedFeatureProcessor {
          public WordsFromPhraseEnd() {
          }
  
          @Override
          public String getName() {
              return "words_from_phrase_end";
          }
  
          @Override
          public String[] getValues() {
              return ZERO_TO_NINETEEN;
          }
  
          /**
           * @param target the target to process
           * @return the number of words until the next major break
           */
          @Override
          public byte process(Target target) {
              Element segment = target.getMaryxmlElement();
              if (segment == null) return 0;
              Element phrase = (Element) DomUtils.getAncestor(segment, MaryXML.PHRASE);
              if (phrase == null) return 0;
              int count = 0;
              TreeWalker tw = DomUtils.createTreeWalker(phrase, MaryXML.TOKEN);
              tw.setCurrentNode(segment);
              Element e;
              while ((e = (Element) tw.nextNode()) != null && count < RAIL_LIMIT) {
                  // only count tokens that have a "ph" attribute
                  if (e.hasAttribute("ph"))
                      count++;
              }
              return (byte) count;
          }
      }
  
      /**
       * Counts the number of words since the start of the sentence.
       */
      public static class WordsFromSentenceStart implements ByteValuedFeatureProcessor {
          public WordsFromSentenceStart() {
          }
  
          @Override
          public String getName() {
              return "words_from_sentence_start";
          }
  
          @Override
          public String[] getValues() {
              return ZERO_TO_NINETEEN;
          }
  
          /**
           * @param target the target to process
           * @return the number of words since the beginning of the sentence
           */
          @Override
          public byte process(Target target) {
              Element segment = target.getMaryxmlElement();
              if (segment == null) return 0;
              Element sentence = (Element) DomUtils.getAncestor(segment, MaryXML.SENTENCE);
              if (sentence == null) return 0;
              int count = 0;
              TreeWalker tw = DomUtils.createTreeWalker(sentence, MaryXML.TOKEN);
              Element word = (Element) DomUtils.getAncestor(segment, MaryXML.TOKEN);
              if (word != null) {
                  tw.setCurrentNode(word);
              } else {
                  tw.setCurrentNode(segment);
              }
              Element e;
              while ((e = (Element) tw.previousNode()) != null && count < RAIL_LIMIT) {
                  // only count tokens that have a "ph" attribute:
                  if (e.hasAttribute("ph"))
                      count++;
              }
              return (byte) count;
          }
      }
  
      /**
       * Counts the number of words until the end of the sentence.
       */
      public static class WordsFromSentenceEnd implements ByteValuedFeatureProcessor {
          public WordsFromSentenceEnd() {
          }
  
          @Override
          public String getName() {
              return "words_from_sentence_end";
          }
  
          @Override
          public String[] getValues() {
              return ZERO_TO_NINETEEN;
          }
  
          /**
           * @param target the target to process
           * @return the number of words until the end of the sentence
           */
          @Override
          public byte process(Target target) {
              Element segment = target.getMaryxmlElement();
              if (segment == null) return 0;
              Element sentence = (Element) DomUtils.getAncestor(segment, MaryXML.SENTENCE);
              if (sentence == null) return 0;
              int count = 0;
              TreeWalker tw = DomUtils.createTreeWalker(sentence, MaryXML.TOKEN);
              tw.setCurrentNode(segment);
              Element e;
              while ((e = (Element) tw.nextNode()) != null && count < RAIL_LIMIT) {
                  // only count tokens that have a "ph" attribute:
                  if (e.hasAttribute("ph"))
                      count++;
              }
              return (byte) count;
          }
      }
  
      /**
       * Counts the number of phrases since the start of the sentence.
       */
      public static class PhrasesFromSentenceStart implements ByteValuedFeatureProcessor {
          public PhrasesFromSentenceStart() {
          }
  
          @Override
          public String getName() {
              return "phrases_from_sentence_start";
          }
  
          @Override
          public String[] getValues() {
              return ZERO_TO_NINETEEN;
          }
  
          /**
           * @param target the target to process
           * @return the number of phrases since the start of the sentence
           */
          @Override
          public byte process(Target target) {
              Element segment = target.getMaryxmlElement();
              if (segment == null) return 0;
              Element sentence = (Element) DomUtils.getAncestor(segment, MaryXML.SENTENCE);
              if (sentence == null) return 0;
              int count = 0;
              TreeWalker tw = DomUtils.createTreeWalker(sentence, MaryXML.PHRASE);
              Element phrase = (Element) DomUtils.getAncestor(segment, MaryXML.PHRASE);
              if (phrase != null) {
                  tw.setCurrentNode(phrase);
              } else {
                  tw.setCurrentNode(segment);
              }
              Element e;
              while ((e = (Element) tw.previousNode()) != null && count < RAIL_LIMIT) {
                  count++;
              }
              return (byte) count;
          }
      }
  
      /**
       * Counts the number of phrases until the end of the sentence.
       */
      public static class PhrasesFromSentenceEnd implements ByteValuedFeatureProcessor {
          public PhrasesFromSentenceEnd() {
          }
  
          @Override
          public String getName() {
              return "phrases_from_sentence_end";
          }
  
          @Override
          public String[] getValues() {
              return ZERO_TO_NINETEEN;
          }
  
          /**
           * @param target the target to process
           * @return the number of phrases until the end of the sentence.
           */
          @Override
          public byte process(Target target) {
              Element segment = target.getMaryxmlElement();
              if (segment == null) return 0;
              Element sentence = (Element) DomUtils.getAncestor(segment, MaryXML.SENTENCE);
              if (sentence == null) return 0;
              int count = 0;
              TreeWalker tw = DomUtils.createTreeWalker(sentence, MaryXML.PHRASE);
              tw.setCurrentNode(segment);
              Element e;
              while ((e = (Element) tw.nextNode()) != null && count < RAIL_LIMIT) {
                  count++;
              }
              return (byte) count;
          }
      }
  
  
      /**
       * Counts the number of syllables since the last accent in the current phrase.
       */
      public static class SylsFromPrevAccent implements ByteValuedFeatureProcessor {
          public SylsFromPrevAccent() {
          }
  
          @Override
          public String getName() {
              return "syls_from_prev_accent";
          }
  
          @Override
          public String[] getValues() {
              return ZERO_TO_NINETEEN;
          }
  
          /**
           * @param target the target to process
           * @return the number of syllables since the last accent
           */
          @Override
          public byte process(Target target) {
              Element segment = target.getMaryxmlElement();
              if (segment == null) return 0;
              Element phrase = (Element) DomUtils.getAncestor(segment, MaryXML.PHRASE);
              if (phrase == null) return 0;
              int count = 0;
              TreeWalker tw = DomUtils.createTreeWalker(phrase, MaryXML.SYLLABLE);
              Element syllable = (Element) DomUtils.getAncestor(segment, MaryXML.SYLLABLE);
              if (syllable != null) {
                  tw.setCurrentNode(syllable);
              } else {
                  tw.setCurrentNode(segment);
              }
              Element e;
              while ((e = (Element) tw.previousNode()) != null && count < RAIL_LIMIT) {
                  count++;
                  String accent = e.getAttribute("accent");
                  if (!accent.equals(""))
                      break;
              }
              return (byte) count;
          }
      }
  
      /**
       * Counts the number of syllables until the next accent in the current phrase.
       */
      public static class SylsToNextAccent implements ByteValuedFeatureProcessor {
          public SylsToNextAccent() {
          }
  
          @Override
          public String getName() {
              return "syls_to_next_accent";
          }
  
          @Override
          public String[] getValues() {
              return ZERO_TO_NINETEEN;
          }
  
          /**
           * @param target the target to process
           * @return the number of syllables until the next accent
           */
          @Override
          public byte process(Target target) {
              Element segment = target.getMaryxmlElement();
              if (segment == null) return 0;
              Element phrase = (Element) DomUtils.getAncestor(segment, MaryXML.PHRASE);
              if (phrase == null) return 0;
              int count = 0;
              TreeWalker tw = DomUtils.createTreeWalker(phrase, MaryXML.SYLLABLE);
              tw.setCurrentNode(segment);
              Element e;
              while ((e = (Element) tw.nextNode()) != null && count < RAIL_LIMIT) {
                  count++;
                  String accent = e.getAttribute("accent");
                  if (!accent.equals(""))
                      break;
              }
              return (byte) count;
          }
      }
  
      /**
       * Counts the number of syllables since the last stressed syllable in the current phrase.
       */
      public static class SylsFromPrevStressed implements ByteValuedFeatureProcessor {
          public SylsFromPrevStressed() {
          }
  
          @Override
          public String getName() {
              return "syls_from_prev_stressed";
          }
  
          @Override
          public String[] getValues() {
              return ZERO_TO_NINETEEN;
          }
  
          /**
           * @param target the target to process
           * @return the number of syllables since the last accent
           */
          @Override
          public byte process(Target target) {
              Element segment = target.getMaryxmlElement();
              if (segment == null) return 0;
              Element phrase = (Element) DomUtils.getAncestor(segment, MaryXML.PHRASE);
              if (phrase == null) return 0;
              int count = 0;
              TreeWalker tw = DomUtils.createTreeWalker(phrase, MaryXML.SYLLABLE);
              Element syllable = (Element) DomUtils.getAncestor(segment, MaryXML.SYLLABLE);
              if (syllable != null) {
                  tw.setCurrentNode(syllable);
              } else {
                  tw.setCurrentNode(segment);
              }
              Element e;
              while ((e = (Element) tw.previousNode()) != null && count < RAIL_LIMIT) {
                  count++;
                  String stress = e.getAttribute("stress");
                  if (stress.equals("1"))
                      break;
              }
              return (byte) count;
          }
      }
  
      /**
       * Counts the number of syllables until the next stressed syllable in the current phrase.
       */
      public static class SylsToNextStressed implements ByteValuedFeatureProcessor {
          public SylsToNextStressed() {
          }
  
          @Override
          public String getName() {
              return "syls_to_next_stressed";
          }
  
          @Override
          public String[] getValues() {
              return ZERO_TO_NINETEEN;
          }
  
          /**
           * @param target the target to process
           * @return the number of syllables until the next stressed syllable
           */
          @Override
          public byte process(Target target) {
              Element segment = target.getMaryxmlElement();
              if (segment == null) return 0;
              Element phrase = (Element) DomUtils.getAncestor(segment, MaryXML.PHRASE);
              if (phrase == null) return 0;
              int count = 0;
              TreeWalker tw = DomUtils.createTreeWalker(phrase, MaryXML.SYLLABLE);
              tw.setCurrentNode(segment);
              Element e;
              while ((e = (Element) tw.nextNode()) != null && count < RAIL_LIMIT) {
                  count++;
                  String stress = e.getAttribute("stress");
                  if (stress.equals("1"))
                      break;
              }
              return (byte) count;
          }
      }
  
      /**
       * Determines the word punctuation.
       */
      public static class WordPunc implements ByteValuedFeatureProcessor {
          protected String name;
          protected TargetElementNavigator navigator;
          protected ByteStringTranslator values;
  
          /**
           * @param name          name of this feature processor
           * @param wordNavigator a navigator which returns a word for a target.
           *                      This navigator decides the word for which the punctuation will be computed.
           */
          public WordPunc(String name, TargetElementNavigator wordNavigator) {
              this.name = name;
              this.navigator = wordNavigator;
              this.values = new ByteStringTranslator(new String[]{
                      "0", ".", ",", ";", ":", "(", ")", "?", "!", "\""
              });
          }
  
          @Override
          public String getName() {
              return name;
          }
  
          @Override
          public String[] getValues() {
              return values.getStringValues();
          }
  
          @Override
          public byte process(Target target) {
              Element word = navigator.getElement(target);
              if (word == null) return 0;
              Element sentence = (Element) DomUtils.getAncestor(word, MaryXML.SENTENCE);
              if (sentence == null) return 0;
              TreeWalker tw = DomUtils.createTreeWalker(sentence, MaryXML.TOKEN, MaryXML.BOUNDARY);
              tw.setCurrentNode(word);
              Element next = (Element) tw.nextNode();
              if (next == null || !next.getTagName().equals(MaryXML.TOKEN) || next.hasAttribute("ph"))
                  return 0;
              String text = MaryDomUtils.tokenText(next);
              if (values.contains(text)) {
                  return values.get(text);
              }
              // unknown or no punctuation: return "0"
              return values.get("0");
          }
      }
  
      /**
       * Determines the next word punctuation in the sentence.
       */
      public static class NextPunctuation extends WordPunc {
          public NextPunctuation() {
              super("next_punctuation", new WordNavigator());
          }
  
          @Override
          public byte process(Target target) {
              Element word = navigator.getElement(target);
              if (word == null) return 0;
              Element sentence = (Element) DomUtils.getAncestor(word, MaryXML.SENTENCE);
              if (sentence == null) return 0;
              TreeWalker tw = DomUtils.createTreeWalker(sentence, MaryXML.TOKEN);
              tw.setCurrentNode(word);
              Element e;
              while ((e = (Element) tw.nextNode()) != null) {
                  if (e.hasAttribute("ph")) // a word
                      continue;
                  // potentially a punctuation
                  String text = MaryDomUtils.tokenText(e);
                  if (values.contains(text)) {
                      return values.get(text);
                  }
              }
              // no next punctuation: return "0"
              return values.get("0");
          }
      }
  
      /**
       * Determines the previous word punctuation in the sentence.
       */
      public static class PrevPunctuation extends WordPunc {
          public PrevPunctuation() {
              super("prev_punctuation", new WordNavigator());
          }
  
          @Override
          public byte process(Target target) {
              Element word = navigator.getElement(target);
              if (word == null) return 0;
              Element sentence = (Element) DomUtils.getAncestor(word, MaryXML.SENTENCE);
              if (sentence == null) return 0;
              TreeWalker tw = DomUtils.createTreeWalker(sentence, MaryXML.TOKEN);
              tw.setCurrentNode(word);
              Element e;
              while ((e = (Element) tw.previousNode()) != null) {
                  if (e.hasAttribute("ph")) // a word
                      continue;
                  // potentially a punctuation
                  String text = MaryDomUtils.tokenText(e);
                  if (values.contains(text)) {
                      return values.get(text);
                  }
              }
              // no next punctuation: return "0"
              return values.get("0");
          }
      }
  
      /**
       * Determines the distance in words to the next word punctuation in the sentence.
       */
      public static class WordsToNextPunctuation extends WordPunc {
          public WordsToNextPunctuation() {
              super("words_to_next_punctuation", new WordNavigator());
          }
  
          @Override
          public String[] getValues() {
              return ZERO_TO_NINETEEN;
          }
  
          @Override
          public byte process(Target target) {
              Element word = navigator.getElement(target);
              if (word == null) return 0;
              Element sentence = (Element) DomUtils.getAncestor(word, MaryXML.SENTENCE);
              if (sentence == null) return 0;
              TreeWalker tw = DomUtils.createTreeWalker(sentence, MaryXML.TOKEN);
              tw.setCurrentNode(word);
              Element e;
              int count = 0;
              while ((e = (Element) tw.nextNode()) != null && count < RAIL_LIMIT) {
                  count++;
                  if (e.hasAttribute("ph")) // a word
                      continue;
                  // potentially a punctuation
                  String text = MaryDomUtils.tokenText(e);
                  if (values.contains(text)) {
                      break;
                  }
              }
              // found punctuation or end of sentence:
              return (byte) count;
          }
      }
  
      /**
       * Determines the distance in words from the previous word punctuation in the sentence.
       */
      public static class WordsFromPrevPunctuation extends WordPunc {
          public WordsFromPrevPunctuation() {
              super("words_from_prev_punctuation", new WordNavigator());
          }
  
          @Override
          public String[] getValues() {
              return ZERO_TO_NINETEEN;
          }
  
          @Override
          public byte process(Target target) {
              Element word = navigator.getElement(target);
              if (word == null) return 0;
              Element sentence = (Element) DomUtils.getAncestor(word, MaryXML.SENTENCE);
              if (sentence == null) return 0;
              TreeWalker tw = DomUtils.createTreeWalker(sentence, MaryXML.TOKEN);
              tw.setCurrentNode(word);
              Element e;
              int count = 0;
              while ((e = (Element) tw.previousNode()) != null && count < RAIL_LIMIT) {
                  count++;
                  if (e.hasAttribute("ph")) // a word
                      continue;
                  // potentially a punctuation
                  String text = MaryDomUtils.tokenText(e);
                  if (values.contains(text)) {
                      break;
                  }
              }
              // found punctuation or start of sentence:
              return (byte) count;
          }
      }
  
  
      /**
       * Determine the prosodic property of a target
       *
       * @author Anna Hunecke
       */
      public static class Selection_Prosody implements ByteValuedFeatureProcessor {
  
          protected TargetElementNavigator navigator;
          protected ByteStringTranslator values = new ByteStringTranslator(new String[]{
                  "0", "stressed", "pre-nuclear", "nuclear", "finalHigh", "finalLow", "final"});
          private Set<String> lowEndtones = new HashSet<String>(Arrays.asList(new String[]{
                  "L-", "L-%", "L-L%"
          }));
          private Set<String> highEndtones = new HashSet<String>(Arrays.asList(new String[]{
                  "H-", "!H-", "H-%", "H-L%", "!H-%", "H-^H%", "!H-^H%", "L-H%", "H-H%"
          }));
  
          public Selection_Prosody(TargetElementNavigator syllableNavigator) {
              this.navigator = syllableNavigator;
          }
  
          @Override
          public String getName() {
              return "selection_prosody";
          }
  
          @Override
          public String[] getValues() {
              return values.getStringValues();
          }
  
          /**
           * Determine the prosodic property of the target
           *
           * @param target the target
           * @return 0 - unstressed, 1 - stressed, 2 - pre-nuclear accent
           * 3 - nuclear accent, 4 - phrase final high, 5 - phrase final low,
           * 6 - phrase final (with unknown high/low status).
           */
          @Override
          public byte process(Target target) {
              //first find out if syllable is stressed
              Element syllable = navigator.getElement(target);
              if (syllable == null) return (byte) 0;
              boolean stressed = false;
              if (syllable.getAttribute("stress").equals("1")) {
                  stressed = true;
              }
              //find out if we have an accent
              boolean accented = syllable.hasAttribute("accent");
              boolean nuclear = true; // relevant only if accented == true
              //find out the position of the target
              boolean phraseFinal = false;
              String endtone = null;
              Element sentence = (Element) DomUtils.getAncestor(syllable, MaryXML.SENTENCE);
              if (sentence == null) return 0;
              TreeWalker tw = DomUtils.createTreeWalker(sentence, MaryXML.SYLLABLE, MaryXML.BOUNDARY);
              tw.setCurrentNode(syllable);
              Element e = (Element) tw.nextNode();
              if (e != null) {
                  if (e.getTagName().equals(MaryXML.BOUNDARY)) {
                      phraseFinal = true;
                      endtone = e.getAttribute("tone");
                  }
                  if (accented) { // look forward for any accent
                      while (e != null) {
                          if (e.getTagName().equals(MaryXML.SYLLABLE) && e.hasAttribute("accent")) {
                              nuclear = false;
                              break;
                          }
                          e = (Element) tw.nextNode();
                      }
                  }
              }
              // Now, we know:
              // stressed or not
              // accented or not
              // if accented, nuclear or not
              // if final, the endtone
  
              if (accented) {
                  if (nuclear) {
                      return values.get("nuclear");
                  } else {
                      return values.get("pre-nuclear");
                  }
              } else if (phraseFinal) {
                  if (endtone != null && highEndtones.contains(endtone)) {
                      return values.get("finalHigh");
                  } else if (endtone != null && lowEndtones.contains(endtone)) {
                      return values.get("finalLow");
                  } else {
                      return values.get("final");
                  }
              } else if (stressed) {
                  return values.get("stressed");
              }
              return (byte) 0;//return unstressed
          }
      }
  
  
      /**
       * Returns the duration of the given segment, in seconds.
       */
      public static class UnitDuration implements ContinuousFeatureProcessor {
          @Override
          public String getName() {
              return "unit_duration";
          }
  
          @Override
          public float process(Target target) {
              if (target instanceof DiphoneTarget) {
                  DiphoneTarget diphone = (DiphoneTarget) target;
                  return process(diphone.left) + process(diphone.right);
              }
              Element seg = target.getMaryxmlElement();
              if (seg == null) {
                  return 0;
              }
              float phoneDuration = 0;
              String sDur;
              if (seg.getTagName().equals(MaryXML.PHONE)) sDur = seg.getAttribute("d");
              else {
                  assert seg.getTagName().equals(MaryXML.BOUNDARY) : "segment should be a phone or a boundary, but is a " + seg.getTagName();
                  sDur = seg.getAttribute("duration");
              }
              if (sDur.equals("")) {
                  return 0;
              }
              try {
                  // parse duration string, and convert from milliseconds into seconds:
                  phoneDuration = Float.parseFloat(sDur) * 0.001f;
              } catch (NumberFormatException nfe) {
              }
              if (target instanceof HalfPhoneTarget)
                  return phoneDuration / 2;
              return phoneDuration;
          }
      }
  
      /**
       * Calculates the log of the fundamental frequency in the middle of a unit segment.
       * This processor should be used by target items only -- for unit features during voice
       * building, the actual measured values should be used.
       */
      public static class UnitLogF0 implements ContinuousFeatureProcessor {
          @Override
          public String getName() {
              return "unit_logf0";
          }
  
          @Override
          public float process(Target target) {
              return process(target, false);
          }
  
          /**
           * Compute log f0 and log f0 delta for the given target.
           *
           * @param target
           * @param delta  if true, return the delta, i.e. the logF0 slope; if false, return the log f0 value itself.
           * @return
           */
          protected float process(Target target, boolean delta) {
              // Note: all variables in this method with "f0" in their name
              // actually represent log f0 values.
              if (target instanceof DiphoneTarget) {
                  DiphoneTarget diphone = (DiphoneTarget) target;
                  return (process(diphone.left) + process(diphone.right)) / 2;
              }
              // Idea: find the closest f0 targets in the current syllable, left and right of our middle;
              // linearly interpolate between them to find the value in the middle of this unit.
              Element seg = target.getMaryxmlElement();
              if (seg == null) {
                  return 0;
              }
              if (!seg.getTagName().equals(MaryXML.PHONE)) {
                  return 0;
              }
              // get mid position of segment wrt phone start (phone start = 0, phone end = phone duration)
              float mid;
              float phoneDuration = getDuration(seg);
              if (target instanceof HalfPhoneTarget) {
                  if (((HalfPhoneTarget) target).isLeftHalf()) {
                      mid = .25f;
                  } else {
                      mid = .75f;
                  }
              } else { // phone target
                  mid = .5f;
              }
  
              // Now mid is the middle of the unit relative to the phone start, in percent
              Float lastPos = null; // position relative to mid, in milliseconds (negative)
              float lastF0 = 0;
              Float nextPos = null; // position relative to mid, in milliseconds
              float nextF0 = 0;
              Float[] f0values = getLogF0Values(seg);
  
              assert f0values != null;
              // values are position, f0, position, f0, etc.; 
              // position is in percent of phone duration between 0 and 1, f0 is in Hz
              for (int i = 0; i < f0values.length; i += 2) {
                  float pos = f0values[i];
                  if (pos <= mid) {
                      lastPos = (pos - mid) * phoneDuration; // negative or zero
                      lastF0 = f0values[i + 1];
                  } else if (pos > mid) {
                      nextPos = (pos - mid) * phoneDuration; // positive
                      nextF0 = f0values[i + 1];
                      break; // no point looking further to the right
                  }
              }
              if (lastPos == null) { // need to look to the left
                  float msBack = -mid * phoneDuration;
                  Element e = seg;
  
                  // get all phone units in the same phrase
                  Element phraseElement = (Element) DomUtils.getAncestor(seg, MaryXML.PHRASE);
                  TreeWalker tw = DomUtils.createTreeWalker(seg.getOwnerDocument(), phraseElement, MaryXML.PHONE);
                  Element en;
                  while ((en = (Element) tw.nextNode()) != null) {
                      if (en == seg) {
                          break;
                      }
                  }
  
                  while ((e = (Element) tw.previousNode()) != null) {
                      float dur = getDuration(e);
                      f0values = getLogF0Values(e);
                      if (f0values.length == 0) {
                          msBack -= dur;
                          continue;
                      }
                      assert f0values.length > 1;
                      float pos = f0values[f0values.length - 2];
                      lastPos = msBack - (1 - pos) * dur;
                      lastF0 = f0values[f0values.length - 1];
                      break;
                  }
              }
  
              if (nextPos == null) { // need to look to the right
                  float msForward = (1 - mid) * phoneDuration;
                  Element e = seg;
  
                  // get all phone units in the same phrase
                  Element phraseElement = (Element) DomUtils.getAncestor(seg, MaryXML.PHRASE);
                  TreeWalker tw = DomUtils.createTreeWalker(seg.getOwnerDocument(), phraseElement, MaryXML.PHONE);
                  Element en;
                  while ((en = (Element) tw.nextNode()) != null) {
                      if (en == seg) {
                          break;
                      }
                  }
  
                  while ((e = (Element) tw.nextNode()) != null) {
                      float dur = getDuration(e);
                      f0values = getLogF0Values(e);
                      if (f0values.length == 0) {
                          msForward += dur;
                          continue;
                      }
                      assert f0values.length > 1;
                      float pos = f0values[0];
                      nextPos = msForward + pos * dur;
                      nextF0 = f0values[1];
                      break;
                  }
              }
  
              if (lastPos == null && nextPos == null) {
                  // no info
                  return 0;
              } else if (lastPos == null) {
                  // have only nextF0;
                  if (delta) return 0;
                  else return nextF0;
              } else if (nextPos == null) {
                  // have only lastF0
                  if (delta) return 0;
                  else return lastF0;
              }
              assert lastPos <= 0 && 0 <= nextPos : "unexpected: lastPos=" + lastPos + ", nextPos=" + nextPos;
              // build a linear function (f(x) = slope*x+intersectionYAxis)
              float f0;
              float slope;
              if (lastPos - nextPos == 0) {
                  f0 = (lastF0 + nextF0) / 2;
                  slope = 0;
              } else {
                  slope = (nextF0 - lastF0) / (nextPos - lastPos);
                  // calculate the pitch
                  f0 = lastF0 + slope * (-lastPos);
              }
              assert !Float.isNaN(f0) : "f0 is not a number";
              assert lastF0 <= f0 && nextF0 >= f0 || lastF0 >= f0 && nextF0 <= f0 : "f0 should be between last and next values";
  
              if (delta) return slope;
              else return f0;
          }
  
          private Float[] getLogF0Values(Element ph) {
              String mbrTargets = ph.getAttribute("f0");
              if (mbrTargets.equals("")) {
                  return new Float[0];
              }
              ArrayList<Float> values = new ArrayList<Float>();
              try {
                  // mbrTargets contains one or more pairs of numbers, 
                  // either enclosed by (a,b) or just separated by whitespace.
                  StringTokenizer st = new StringTokenizer(mbrTargets, " (,)");
                  while (st.hasMoreTokens()) {
                      String posString = "";
                      while (st.hasMoreTokens() && posString.equals("")) posString = st.nextToken();
                      String f0String = "";
                      while (st.hasMoreTokens() && f0String.equals("")) f0String = st.nextToken();
  
                      float pos = Float.parseFloat(posString) * 0.01f;
                      assert 0 <= pos && pos <= 1 : "invalid position:" + pos + " (pos string was '" + posString + "' coming from '" + mbrTargets + "')";
                      float f0 = Float.parseFloat(f0String);
                      float logF0 = (float) Math.log(f0);
                      values.add(pos);
                      values.add(logF0);
                  }
              } catch (Exception e) {
                  return new Float[0];
              }
              return values.toArray(new Float[0]);
          }
  
          private float getDuration(Element ph) {
              float phoneDuration = 0;
              String sDur = ph.getAttribute("d");
              if (!sDur.equals("")) {
                  try {
                      phoneDuration = Float.parseFloat(sDur);
                  } catch (NumberFormatException nfe) {
                  }
              }
              return phoneDuration;
          }
      }
  
      /**
       * Calculates the slope of a linear approximation of the fundamental frequency, in the log domain.
       * The slope is computed by linearly connecting the two log f0 values closest to the middle of the unit segment.
       * This processor should be used by target items only -- for unit features during voice
       * building, the actual measured values should be used.
       */
      public static class UnitLogF0Delta extends UnitLogF0 {
          @Override
          public String getName() {
              return "unit_logf0delta";
          }
  
          @Override
          public float process(Target target) {
              return process(target, true);
          }
      }
  
      /**
       * Returns the value of the given feature for the given segment.
       */
      public static class GenericContinuousFeature implements ContinuousFeatureProcessor {
          private String name;
          private String attributeName;
  
          public GenericContinuousFeature(String featureName, String attributeName) {
              this.name = featureName;
              this.attributeName = attributeName;
          }
  
          @Override
          public String getName() {
              return name;
          }
  
          @Override
          public float process(Target target) {
              if (target instanceof DiphoneTarget) {
                  DiphoneTarget diphone = (DiphoneTarget) target;
                  // return mean of left and right costs:
                  return (process(diphone.left) + process(diphone.right)) / 2.0f;
              }
              Element seg = target.getMaryxmlElement();
              if (seg == null) {
                  return 0;
              }
              float value = 0;
              String valueString;
              if (seg.getTagName().equals(MaryXML.PHONE)) {
                  valueString = seg.getAttribute(attributeName);
              } else {
                  assert seg.getTagName().equals(MaryXML.BOUNDARY) : "segment should be a phone or a boundary, but is a " + seg.getTagName();
                  valueString = seg.getAttribute(attributeName);
              }
              if (valueString.equals("")) {
                  return 0;
              }
              try {
                  value = Float.parseFloat(valueString);
              } catch (NumberFormatException nfe) {
                  return 0;
              }
              return value;
          }
      }
  
  }
  