public void extend(TemplateDocument doc, FragmentList f, boolean normalize)
      throws BehaviorInstantiationException {
    ParentNode extended;
    if (extendedTag == null) {
      extended = extendedDocument;
    } else {
      extended = extendedTag;
    }

    ArrayList<TemplateNode> children = doc.getChildren();
    for (TemplateNode node : children) {
      if (node instanceof Tag) {
        Tag tag = (Tag) node;
        Tag overriden = null;

        Attribute overrides =
            tag.getDynamicAttribute(
                new DynamicAttributeKey(Cambridge.DefaultNamespaceURI, "a", "overrides"));
        if (overrides != null) {
          overriden = extendedDocument.locateTag(overrides.getValue());
        } else {
          Attribute id = tag.getAttribute("id");
          if (id == null && tag.isDynamic()) {
            id = tag.getAttribute(tag.getNameSpace(), "id");
          }

          if (id != null) {
            overriden = extended.getElementById(id.getValue());
          }
        }
        if (overriden != null) {
          overriden.getParent().replaceChild((TemplateNode) overriden, (TemplateNode) tag);
        }
      } else if (node instanceof SetDirective) {
        int index = 0;
        if (extended == extendedDocument
            && extendedDocument.getChildren().size() > 0
            && extendedDocument.getChildren().get(0) instanceof ExtendsDirective) {
          index = 1;
        }

        extended.insertChild(index, node);
      }
    }

    if (extendedDocument.getChildren().size() > 0
        && extendedDocument.getChildren().get(0) instanceof ExtendsDirective) {
      ((ExtendsDirective) extendedDocument.getChildren().get(0)).extend(extendedDocument, f, false);
    }

    if (normalize) {
      if (extendedTag != null) {
        extendedTag.normalize(extendedDocument, f);
      } else {
        f.addAll(extendedDocument.normalize());
      }
    }
  }
  public boolean getSnippets(
      final int docId,
      final ReaderInterface reader,
      final List<FieldValueItem> values,
      final List<FieldValueItem> snippets,
      final Timer parentTimer)
      throws IOException, ParseException, SyntaxError, SearchLibException {

    if (values == null) return false;

    final Timer timer = new Timer(parentTimer, "SnippetField " + this.name);
    final long halfTimeExpiration =
        this.timeLimit == 0 ? 0 : timer.getStartOffset(this.timeLimit / 2);
    final long expiration = this.timeLimit == 0 ? 0 : timer.getStartOffset(this.timeLimit);

    FragmenterAbstract fragmenter = fragmenterTemplate.newInstance();
    SnippetVector currentVector = null;

    Timer t = new Timer(timer, "extractTermVectorIterator");

    Iterator<SnippetVector> vectorIterator =
        SnippetVectors.extractTermVectorIterator(
            docId, reader, snippetQueries, name, values, indexAnalyzer, t, halfTimeExpiration);
    if (vectorIterator != null)
      currentVector = vectorIterator.hasNext() ? vectorIterator.next() : null;

    t.end(null);

    t = new Timer(timer, "getFraments");

    int startOffset = 0;
    FragmentList fragments = new FragmentList();
    int vectorOffset = 0;
    for (FieldValueItem valueItem : values) {
      String value = valueItem.getValue();
      if (value != null) {
        // VectorOffset++ depends of EndOffset bug #patch Lucene 579 and
        // 1458
        fragmenter.getFragments(value, fragments, vectorOffset++);
      }
    }

    t.end(null);

    if (fragments.size() == 0) {
      timer.end(null);
      return false;
    }

    t = new Timer(timer, "checkValue");

    Fragment fragment = fragments.first();
    while (fragment != null) {
      currentVector = checkValue(currentVector, vectorIterator, startOffset, fragment);
      startOffset += fragment.getOriginalText().length();
      fragment = fragment.next();
    }

    t.end(null);

    Timer sbTimer = new Timer(timer, "snippetBuilder");

    boolean result = false;
    int snippetCounter = maxSnippetNumber;
    int scoredFragment = 0;
    while (snippetCounter-- != 0) {
      Fragment bestScoreFragment = null;
      fragment = Fragment.findNextHighlightedFragment(fragments.first());
      List<Fragment> scoreFragments = new ArrayList<Fragment>(0);
      double maxSearchScore = 0;

      t = new Timer(sbTimer, "fragmentScore");
      boolean expired = false;

      while (fragment != null) {
        double sc = fragment.searchScore(name, queryAnalyzer, query);
        if (sc > maxSearchScore) maxSearchScore = sc;
        scoreFragments.add(fragment);
        fragment = Fragment.findNextHighlightedFragment(fragment.next());
        scoredFragment++;
        if (expiration != 0) {
          if (System.currentTimeMillis() > expiration) {
            expired = true;
            break;
          }
        }
      }

      t.end("fragmentScore " + scoredFragment + " " + expired);

      for (Fragment frag : scoreFragments)
        bestScoreFragment =
            Fragment.bestScore(bestScoreFragment, frag, maxSearchScore, maxSnippetSize);

      if (bestScoreFragment != null) {
        SnippetBuilder snippetBuilder =
            new SnippetBuilder(maxSnippetSize, unescapedSeparator, tags, bestScoreFragment);
        if (snippetBuilder.length() > 0)
          snippets.add(new FieldValueItem(FieldValueOriginEnum.SNIPPET, snippetBuilder.toString()));
        fragments.remove(snippetBuilder.getFragments());
        result = true;
        continue;
      }

      if (fragments.first() == null) break;
      SnippetBuilder snippetBuilder =
          new SnippetBuilder(maxSnippetSize, unescapedSeparator, tags, fragments.first());
      if (snippetBuilder.length() > 0) {
        snippets.add(new FieldValueItem(FieldValueOriginEnum.SNIPPET, snippetBuilder.toString()));
        fragments.remove(snippetBuilder.getFragments());
      }
    }

    sbTimer.end(null);

    timer.end(null);

    return result;
  }