package com.jclark.xsl.expr;

import com.jclark.xsl.om.*;
import java.util.Hashtable;

public class ExprParser extends ExprTokenizer implements NamespaceConstants {
  private NamespacePrefixMap prefixMap;
  private Node node;
  private VariableSet locals;
  private boolean usesCurrentFunction = false;

  static final private Hashtable axisTable = new Hashtable();
  static final private AxisExpr childAxis = new ChildAxisExpr();
  static final private AxisExpr parentAxis = new ParentAxisExpr();
  static final private AxisExpr selfAxis = new SelfAxisExpr();
  static final private AxisExpr attributeAxis = new AttributeAxisExpr();
  static final private AxisExpr descendantOrSelfAxis = new DescendantOrSelfAxisExpr();
  
  static final private Hashtable functionTable = new Hashtable();
  static final private Hashtable extensionFunctionTable = new Hashtable();

  static final private Relation equalsRelation = new EqualsRelation();
  static final private Relation notEqualsRelation = new NotEqualsRelation();
  static final private Relation greaterThanEqualsRelation = new GreaterThanEqualsRelation();
  static final private Relation greaterThanRelation = new GreaterThanRelation();
  static final private Function currentFunction = new CurrentFunction();

  static {
    axisTable.put("child", childAxis);
    axisTable.put("parent", parentAxis);
    axisTable.put("self", selfAxis);
    axisTable.put("attribute", attributeAxis);
    axisTable.put("descendant-or-self", descendantOrSelfAxis);
    axisTable.put("descendant", new DescendantAxisExpr());
    axisTable.put("ancestor-or-self", new AncestorOrSelfAxisExpr());
    axisTable.put("ancestor", new AncestorAxisExpr());
    axisTable.put("following-sibling", new FollowingSiblingAxisExpr());
    axisTable.put("preceding-sibling", new PrecedingSiblingAxisExpr());
    axisTable.put("following", new FollowingAxisExpr());
    axisTable.put("preceding", new PrecedingAxisExpr());

    functionTable.put("boolean", new BooleanFunction());
    functionTable.put("ceiling", new CeilingFunction());
    functionTable.put("concat", new ConcatFunction());
    functionTable.put("contains", new ContainsFunction());
    functionTable.put("count", new CountFunction());
    functionTable.put("document", new DocumentFunction());
    functionTable.put("false", new FalseFunction());
    functionTable.put("floor", new FloorFunction());
    functionTable.put("format-number", new FormatNumberFunction());
    functionTable.put("function-available",
		      new FunctionAvailableFunction());
    // functionTable.put("element-available",
    //		      new ElementAvailableFunction());
    functionTable.put("generate-id", new GenerateIdFunction());
    functionTable.put("id", new IdFunction());
    //functionTable.put("key", new KeyFunction());
    functionTable.put("lang", new LangFunction());
    functionTable.put("last", new LastFunction());
    functionTable.put("local-name", new LocalNameFunction());
    functionTable.put("namespace-uri", new NamespaceUriFunction());
    functionTable.put("normalize-space", new NormalizeSpaceFunction());
    functionTable.put("not", new NotFunction());
    functionTable.put("number", new NumberFunction());
    functionTable.put("position", new PositionFunction());
    functionTable.put("name", new NameFunction());
    functionTable.put("round", new RoundFunction());
    functionTable.put("starts-with", new StartsWithFunction());
    functionTable.put("string", new StringFunction());
    functionTable.put("string-length", new StringLengthFunction());
    functionTable.put("substring", new SubstringFunction());
    functionTable.put("substring-after", new SubstringAfterFunction());
    functionTable.put("substring-before", new SubstringBeforeFunction());
    functionTable.put("sum", new SumFunction());
    functionTable.put("system-property", new SystemPropertyFunction());
    functionTable.put("translate", new TranslateFunction());
    functionTable.put("true", new TrueFunction());
    functionTable.put("unparsed-entity-uri", new UnparsedEntityURIFunction());
    extensionFunctionTable.put("node-set", new NodeSetFunction());
    extensionFunctionTable.put("intersection", new IntersectionFunction());
    extensionFunctionTable.put("difference", new DifferenceFunction());
  }

  public static NodeSetExpr parseNodeSetExpr(Node node, String expr, VariableSet locals) throws XSLException {
    try {
      return parseConvertibleExpr(node, expr, locals).makeNodeSetExpr();
    }
    catch (ParseException e) {
      throw makeXSLException(e, node);
    }
  }

  public static StringExpr parseStringExpr(Node node, String expr, VariableSet locals) throws XSLException {
    try {
      return parseConvertibleExpr(node, expr, locals).makeStringExpr();
    }
    catch (ParseException e) {
      throw makeXSLException(e, node);
    }
  }

  public static NumberExpr parseNumberExpr(Node node, String expr, VariableSet locals) throws XSLException {
    try {
      return parseConvertibleExpr(node, expr, locals).makeNumberExpr();
    }
    catch (ParseException e) {
      throw makeXSLException(e, node);
    }
  }

  public static BooleanExpr parseBooleanExpr(Node node, String expr, VariableSet locals) throws XSLException {
    try {
      return parseConvertibleExpr(node, expr, locals).makeBooleanExpr();
    }
    catch (ParseException e) {
      throw makeXSLException(e, node);
    }
  }

  public static VariantExpr parseVariantExpr(Node node, String expr, VariableSet locals) throws XSLException {
    try {
      return parseConvertibleExpr(node, expr, locals).makeVariantExpr();
    }
    catch (ParseException e) {
      throw makeXSLException(e, node);
    }
  }

  private static ConvertibleExpr parseConvertibleExpr(Node node, String expr, VariableSet locals)
    throws ParseException {
    return new ExprParser(expr, node, locals).parseExpr();
  }

  private ExprParser(String expr, Node node, VariableSet locals) {
    super(expr);
    this.node = node;
    if (node != null)
      prefixMap = node.getNamespacePrefixMap();
    this.locals = locals;
  }

  private ConvertibleExpr parseExpr() throws ParseException {
    next();
    ConvertibleExpr expr = parseOrExpr();
    if (currentToken != TOK_EOF)
      throw new ParseException("unexpected token");
    if (usesCurrentFunction)
      return new WithCurrentExpr(expr);
    return expr;
  }

  private static XSLException makeXSLException(ParseException e, Node node) {
    return new XSLException(e.getMessage(), node);
  }

  private ConvertibleExpr parseOrExpr() throws ParseException {
    ConvertibleExpr expr = parseAndExpr();
    while (currentToken == TOK_OR) {
      next();
      expr = new OrExpr(expr.makeBooleanExpr(),
			parseAndExpr().makeBooleanExpr());
    }
    return expr;
  }

  private ConvertibleExpr parseAndExpr() throws ParseException {
    ConvertibleExpr expr = parseEqualityExpr();
    while (currentToken == TOK_AND) {
      next();
      expr = new AndExpr(expr.makeBooleanExpr(),
			 parseEqualityExpr().makeBooleanExpr());
    }
    return expr;
  }

  private ConvertibleExpr parseEqualityExpr() throws ParseException {
    ConvertibleExpr expr = parseRelationalExpr();
  loop:
    for (;;) {
      switch (currentToken) {
      case TOK_EQUALS:
	next();
	expr = makeRelationalExpr(equalsRelation,
				  expr,
				  parseRelationalExpr());
	break;
      case TOK_NOT_EQUALS:
	next();
	expr = makeRelationalExpr(notEqualsRelation,
				  expr,
				  parseRelationalExpr());
	break;
      default:
	break loop;
      }
    }
    return expr;
  }

  private ConvertibleExpr parseRelationalExpr() throws ParseException {
    ConvertibleExpr expr = parseAdditiveExpr();
  loop:
    for (;;) {
      switch (currentToken) {
      case TOK_GT:
	next();
	expr = makeRelationalExpr(greaterThanRelation,
				  expr,
				  parseAdditiveExpr());
	break;
      case TOK_GTE:
	next();
	expr = makeRelationalExpr(greaterThanEqualsRelation,
				  expr,
				  parseAdditiveExpr());
	break;
      case TOK_LT:
	next();
	expr = makeRelationalExpr(greaterThanRelation,
				  parseAdditiveExpr(),
				  expr);
	break;
      case TOK_LTE:
	next();
	expr = makeRelationalExpr(greaterThanEqualsRelation,
				  parseAdditiveExpr(),
				  expr);
	break;
      default:
	break loop;
      }
    }
    return expr;
  }
  
  private ConvertibleExpr parseAdditiveExpr() throws ParseException {
    ConvertibleExpr expr = parseMultiplicativeExpr();
  loop:
    for (;;) {
      switch (currentToken) {
      case TOK_PLUS:
	next();
	expr = new AddExpr(expr.makeNumberExpr(), parseMultiplicativeExpr().makeNumberExpr());
	break;
      case TOK_MINUS:
	next();
	expr = new SubtractExpr(expr.makeNumberExpr(),
				parseMultiplicativeExpr().makeNumberExpr());
	break;
      default:
	break loop;
      }
    }
    return expr;
  }

  private ConvertibleExpr parseMultiplicativeExpr() throws ParseException {
    ConvertibleExpr expr = parseUnaryExpr();
  loop:
    for (;;) {
      switch (currentToken) {
      case TOK_DIV:
	next();
	expr = new DivideExpr(expr.makeNumberExpr(),
			      parseUnaryExpr().makeNumberExpr());
 	break;
      case TOK_MOD:
	next();
	expr = new ModuloExpr(expr.makeNumberExpr(),
			      parseUnaryExpr().makeNumberExpr());
	break;
      case TOK_MULTIPLY:
	next();
	expr = new MultiplyExpr(expr.makeNumberExpr(),
				parseUnaryExpr().makeNumberExpr());
	break;
      default:
	break loop;
      }
    }
    return expr;
  }

  private ConvertibleExpr parseUnaryExpr() throws ParseException {
    if (currentToken == TOK_MINUS) {
      next();
      return new NegateExpr(parseUnaryExpr().makeNumberExpr());
    }
    return parseUnionExpr();
  }

  private ConvertibleExpr parseUnionExpr() throws ParseException {
    ConvertibleExpr expr = parsePathExpr();
    while (currentToken == TOK_VBAR) {
      next();
      expr = new UnionExpr(expr.makeNodeSetExpr(),
			   parsePathExpr().makeNodeSetExpr());
    }
    return expr;
  }

  private ConvertibleExpr parsePathExpr() throws ParseException {
    if (tokenStartsStep())
      return parseRelativeLocationPath();
    if (currentToken == TOK_SLASH) {
      next();
      if (tokenStartsStep())
	return new RootExpr(parseRelativeLocationPath());
      return new RootExpr(selfAxis);
    }
    if (currentToken == TOK_SLASH_SLASH) {
      next();
      return new RootExpr(descendantOrSelfAxis.compose(parseRelativeLocationPath()));
    }
    ConvertibleExpr expr = parsePrimaryExpr();
    while (currentToken == TOK_LSQB) {
      next();
      expr = new FilterExpr(expr.makeNodeSetExpr(),
			    parseOrExpr().makePredicateExpr());
      expectRsqb();
    }
    if (currentToken == TOK_SLASH) {
      next();
      return expr.makeNodeSetExpr().compose(parseRelativeLocationPath());
    }
    else if (currentToken == TOK_SLASH_SLASH) {
      next();
      return expr.makeNodeSetExpr().compose(descendantOrSelfAxis.compose(parseRelativeLocationPath()));
    }
    else
      return expr;
  }

  private ConvertibleNodeSetExpr parseRelativeLocationPath() throws ParseException {
    ConvertibleNodeSetExpr step = parseStep();
    if (currentToken == TOK_SLASH) {
      next();
      return step.compose(parseRelativeLocationPath());
    }
    if (currentToken == TOK_SLASH_SLASH) {
      next();
      return step.compose(descendantOrSelfAxis.compose(parseRelativeLocationPath()));
    }
    return step;
  }

  private ConvertibleNodeSetExpr parseStep() throws ParseException {
    switch (currentToken) {
    case TOK_AXIS:
      {
	AxisExpr axis = (AxisExpr)axisTable.get(currentTokenValue);
	if (axis == null)
	  throw new ParseException("no such axis");
	boolean isAttribute = currentTokenValue.equals("attribute");
	next();
	return parsePredicates(axis, parseNodeTest(isAttribute));
      }
    case TOK_DOT:
      next();
      return selfAxis;
    case TOK_DOT_DOT:
      next();
      return parentAxis;
    case TOK_AT:
      next();
      return parsePredicates(attributeAxis, parseNodeTest(true));
    default:
      return parsePredicates(childAxis, parseNodeTest(false));
    }
  }

  private ConvertibleNodeSetExpr parsePredicates(AxisExpr axis, Pattern nodeTest) throws ParseException {
    ConvertibleNodeSetExpr expr = axis;
    if (nodeTest != null)
      expr = new NodeTestExpr(expr, nodeTest);
    while (currentToken == TOK_LSQB) {
      next();
      expr = new FilterExpr(expr, parseOrExpr().makePredicateExpr());
      expectRsqb();
    }
    return axis.makeDocumentOrderExpr(expr);
  }

  /* return null if the test is vacuous */

  private PathPatternBase parseNodeTest(boolean isAttributeAxis) throws ParseException {
    PathPatternBase nodeTest;
    switch (currentToken) {
    case TOK_QNAME:
      if (isAttributeAxis)
	nodeTest = new AttributeTest(expandName());
      else
	nodeTest = new ElementTest(expandName());
      break;
    case TOK_STAR:
      nodeTest = isAttributeAxis ? null : new NodeTypeTest(Node.ELEMENT);
      break;
    case TOK_NAME_COLON_STAR:
      if (isAttributeAxis)
	nodeTest = new NamespaceAttributeTest(expandPrefix());
      else
	nodeTest = new NamespaceElementTest(expandPrefix());
      break;
    case TOK_PROCESSING_INSTRUCTION_LPAR:
      next();
      if (currentToken == TOK_LITERAL) {
	nodeTest = new ProcessingInstructionTest(expandName());
	next();
      }
      else
	nodeTest = new NodeTypeTest(Node.PROCESSING_INSTRUCTION);
      expectRpar();
      return nodeTest;
    case TOK_COMMENT_LPAR:
      next();
      expectRpar();
      return new NodeTypeTest(Node.COMMENT);
    case TOK_TEXT_LPAR:
      next();
      expectRpar();
      return new NodeTypeTest(Node.TEXT);
    case TOK_NODE_LPAR:
      next();
      expectRpar();
      return null;
    default:
      throw new ParseException("expected node test");
    }
    next();
    return nodeTest;
  }

  private final void expectRpar() throws ParseException {
    if (currentToken != TOK_RPAR)
      throw new ParseException("expected )");
    next();
  }

  private final void expectRsqb() throws ParseException {
    if (currentToken != TOK_RSQB)
      throw new ParseException("expected ]");
    next();
  }

  private ConvertibleExpr parsePrimaryExpr() throws ParseException {
    ConvertibleExpr expr;
    switch (currentToken) {
    case TOK_VARIABLE_REF:
      {
	Name name = expandName();
	if (locals.contains(name))
	  expr = new LocalVariableRefExpr(name);
	else
	  expr = new GlobalVariableRefExpr(name, node);
	break;
      }
    case TOK_LPAR:
      next();
      expr = parseOrExpr();
      expectRpar();
      return expr;
    case TOK_LITERAL:
      expr = new LiteralExpr(currentTokenValue);
      break;
    case TOK_NUMBER:
      expr = new NumberConstantExpr(Converter.toNumber(currentTokenValue));
      break;
    case TOK_FUNCTION_LPAR:
      {
	Function function = (Function)functionTable.get(currentTokenValue);
	if (function == null) {
	  if (!currentTokenValue.equals("current"))
	    throw new ParseException("no such function: " + currentTokenValue);
	  usesCurrentFunction = true;
	  function = currentFunction;
	}
	next();
	return function.makeCallExpr(parseArgs(), node);
      }
    case TOK_CNAME_LPAR:
      {
	Name name = expandName();
	next();
	if (XT_NAMESPACE.equals(name.getNamespace())) {
	  Function function = (Function)extensionFunctionTable.get(name.getLocalPart());
	  if (function != null)
	    return function.makeCallExpr(parseArgs(), node);
	}
	ConvertibleExpr[] args = parseArgs();
	VariantExpr[] variantArgs = new VariantExpr[args.length];
	for (int i = 0; i < args.length; i++)
	  variantArgs[i] = args[i].makeVariantExpr();
	return new ExtensionFunctionCallExpr(name, variantArgs);
      }
    default:
      throw new ParseException("syntax error");
    }
    next();
    return expr;
  }

  ConvertibleExpr[] parseArgs() throws ParseException {
    if (currentToken == TOK_RPAR) {
      next();
      return new ConvertibleExpr[0];
    }
    ConvertibleExpr[] args = new ConvertibleExpr[1];
    for (;;) {
      args[args.length - 1] = parseOrExpr();
      if (currentToken != TOK_COMMA)
	break;
      next();
      ConvertibleExpr[] oldArgs = args;
      args = new ConvertibleExpr[oldArgs.length + 1];
      System.arraycopy(oldArgs, 0, args, 0, oldArgs.length);
    } 
    expectRpar();
    return args;
  }

  private boolean tokenStartsNodeTest() {
    switch (currentToken) {
    case TOK_QNAME:
    case TOK_STAR:
    case TOK_NAME_COLON_STAR:
    case TOK_PROCESSING_INSTRUCTION_LPAR:
    case TOK_COMMENT_LPAR:
    case TOK_TEXT_LPAR:
    case TOK_NODE_LPAR:
      return true;
    }
    return false;
  }

  private boolean tokenStartsStep() {
    switch (currentToken) {
    case TOK_AXIS:
    case TOK_DOT:
    case TOK_DOT_DOT:
    case TOK_AT:
      return true;
    }
    return tokenStartsNodeTest();
  }

  private Name expandName() throws ParseException {
    try {
      if (prefixMap != null)
	return prefixMap.expandAttributeName(currentTokenValue, null);
      else
	return null;
    }
    catch (XSLException e) {
      throw new ParseException("undefined prefix");
    }
  }

  private String expandPrefix() throws ParseException {
    if (prefixMap == null)
      return null;
    String ns = prefixMap.getNamespace(currentTokenValue);
    if (ns == null)
      throw new ParseException("undefined prefix");
    return ns;
  }

  ConvertibleExpr makeRelationalExpr(Relation rel,
				     ConvertibleExpr e1,
				     ConvertibleExpr e2)
    throws ParseException {
    // OPT: have some more expressions for non-variant cases
    if (e1 instanceof NodeSetExpr
	|| e2 instanceof NodeSetExpr
	|| e1 instanceof VariantExpr
	|| e2 instanceof VariantExpr)
      return new VariantRelationalExpr(rel,
				       e1.makeVariantExpr(),
				       e2.makeVariantExpr());
    if (rel instanceof NumericRelation)
      return new NumberRelationalExpr(rel,
				      e1.makeNumberExpr(),
				      e2.makeNumberExpr());
    if (e1 instanceof BooleanExpr || e2 instanceof BooleanExpr)
      return new BooleanRelationalExpr(rel,
				       e1.makeBooleanExpr(),
				       e2.makeBooleanExpr());
    if (e1 instanceof NumberExpr || e2 instanceof NumberExpr)
      return new NumberRelationalExpr(rel,
				      e1.makeNumberExpr(),
				      e2.makeNumberExpr());
    return new StringRelationalExpr(rel,
				    e1.makeStringExpr(),
				    e2.makeStringExpr());
  }

  private static int findExprEnd(String value, int i) {
    int valueLen = value.length();
    char quote = '\0';
    for (; i < valueLen; i++) {
      char c = value.charAt(i);
      switch (c) {
      case '}':
	if (quote == '\0')
	  return i;
	break;
      case '\"':
      case '\'':
	if (quote == c)
	  quote = '\0';
	else if (quote == '\0')
	  quote = c;
        break;
      }
    }
    return -1;
  }

  public static StringExpr parseAttributeValueTemplate(Node node, String value, VariableSet locals) throws XSLException {
    try {
      ConvertibleStringExpr prev = null;
      StringBuffer buf = new StringBuffer();
      int valueLen = value.length();
      for (int i = 0; i < valueLen; i++) {
	char c = value.charAt(i);
	switch (c) {
	case '{':
	  if (i + 1 < valueLen && value.charAt(i + 1) == '{') {
	    i++;
	    buf.append('{');
	  }
	  else {
	    int n = findExprEnd(value, i + 1);
	    if (n < 0)
	      throw new XSLException("missing }", node);
	    ConvertibleStringExpr expr
	      = parseConvertibleExpr(node,
				     value.substring(i + 1, n),
				     locals).makeStringExpr();
	    if (buf.length() > 0) {
	      if (prev == null)
		prev = new LiteralExpr(buf.toString());
	      else
		prev = new AppendExpr(prev,
				      new LiteralExpr(buf.toString()));
	      buf.setLength(0);
	    }
	    if (prev == null)
	      prev = expr;
	    else
	      prev = new AppendExpr(prev, expr);
	    i = n;
	  }
	  break;
	case '}':
	  buf.append('}');
	  if (i + 1 < valueLen && value.charAt(i + 1) == '}')
	    i++;
	  break;
	default:
	  buf.append(c);
	  break;
	}
      }
      if (buf.length() > 0) {
	if (prev == null)
	  return new LiteralExpr(buf.toString());
	else
	  return new AppendExpr(prev, new LiteralExpr(buf.toString()));
      }
      if (prev != null)
	return prev;
      return new LiteralExpr("");
    }
    catch (ParseException e) {
      throw makeXSLException(e, node);
    }
  }

  public static NodeSetExpr getChildrenExpr() {
    return new ChildAxisExpr();
  }

  public static TopLevelPattern parsePattern(Node node, String pattern) throws XSLException {
    return new ExprParser(pattern, node, new EmptyVariableSet())
      .parseTopLevelPattern(node);
  }

  public static TopLevelPattern parsePattern(Node node, String pattern, VariableSet locals) throws XSLException {
    return new ExprParser(pattern, node, locals)
      .parseTopLevelPattern(node);
  }

  private TopLevelPattern parseTopLevelPattern(Node node) throws XSLException {
    try {
      next();
      TopLevelPattern pattern = parsePathPattern();
      while (currentToken == TOK_VBAR) {
	next();
	pattern = new AlternativesPattern(pattern, parsePathPattern());
      }
      if (currentToken != TOK_EOF)
	throw new ParseException("unexpected token");
      if (usesCurrentFunction)
	throw new ParseException("current() in match pattern");
      return pattern;
    }
    catch (ParseException e) {
      throw makeXSLException(e, node);
    }
  }
  
  private PathPatternBase parsePathPattern() throws ParseException {
    Pattern parent = null;
    switch (currentToken) {
    case TOK_SLASH_SLASH:
      next();
      break;
    case TOK_SLASH:
      next();
      if (!tokenStartsStep())
	return new NodeTypeTest(Node.ROOT);
      parent = new NodeTypeTest(Node.ROOT);
      break;
    case TOK_FUNCTION_LPAR:
      if (currentTokenValue.equals("id")) {
	next();
	if (currentToken != TOK_LITERAL)
	  throw new ParseException("expected literal");
	PathPatternBase tem = new IdPattern(currentTokenValue);
	next();
	expectRpar();
	if (currentToken == TOK_SLASH)
	  parent = tem;
	else if (currentToken == TOK_SLASH_SLASH)
	  parent = new InheritPattern(tem);
	else
	  return tem;
	next();
	break;
      }
      // FIXME support key()
      throw new ParseException("function illegal in pattern");
    default:
      break;
    }
    for (;;) {
      PathPatternBase tem = parseStepPattern();
      if (parent != null)
	tem = new ParentPattern(tem, parent);
      if (currentToken == TOK_SLASH)
	parent = tem;
      else if (currentToken == TOK_SLASH_SLASH)
	parent = new InheritPattern(tem);
      else
	return tem;
      next();
    }
  }

  private PathPatternBase parseStepPattern() throws ParseException {
    PathPatternBase pattern;
    if (currentToken == TOK_AT
	|| (currentToken == TOK_AXIS
	    && currentTokenValue.equals("attribute"))) {
      next();
      pattern = parseNodeTest(true);
      if (pattern == null)
	pattern = new NodeTypeTest(Node.ATTRIBUTE);
    }
    else {
      if (currentToken == TOK_AXIS
	  && currentTokenValue.equals("child"))
	next();
      pattern = parseNodeTest(false);
      // FIXME implement this
      if (pattern == null)
	throw new ParseException("node() in step pattern not implemented");
    }
    while (currentToken == TOK_LSQB) {
      next();
      pattern = new FilterPattern(pattern,
				  parseOrExpr().makePredicateExpr());
      expectRsqb();
    }
    return pattern;
  }

  static boolean functionAvailable(Name name, ExprContext context) throws XSLException {
    String ns = name.getNamespace();
    if (ns == null)
      return functionTable.get(name.getLocalPart()) != null;
    if (ns.equals(XT_NAMESPACE)
	&& extensionFunctionTable.get(name.getLocalPart()) != null)
      return true;
    return context.getExtensionContext(ns).available(name.getLocalPart());
  }
}
