package com.jclark.xsl.tr;

import com.jclark.xsl.om.*;
import com.jclark.xsl.expr.Pattern;
import com.jclark.xsl.expr.ExprParser;
import com.jclark.xsl.expr.StringExpr;
import com.jclark.xsl.expr.NodeSetExpr;
import com.jclark.xsl.expr.BooleanExpr;
import com.jclark.xsl.expr.VariantExpr;
import com.jclark.xsl.expr.Variant;
import com.jclark.xsl.expr.StringVariant;
import com.jclark.xsl.expr.NumberVariant;
import com.jclark.xsl.expr.VariableSet;
import com.jclark.xsl.expr.EmptyVariableSet;
import com.jclark.xsl.expr.ExtensionContext;
import com.jclark.xsl.util.Comparator;
import com.jclark.xsl.util.BilevelComparator;
import com.jclark.xsl.util.TextComparator;
import com.jclark.xsl.util.NumberComparator;
import com.jclark.xsl.util.ReverseComparator;
import java.io.IOException;
import java.net.URL;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;
import java.util.StringTokenizer;
import java.util.Locale;
import java.text.Collator;

class SheetImpl implements SheetDetails, LoadContext {
  private
  interface TopLevelParser {
    void parse(Node node) throws XSLException, IOException;
  }

  private
  class IncludeParser implements TopLevelParser {
    public void parse(Node ruleNode) throws XSLException, IOException {
      Node sheetNode = parser.load(new URL(ruleNode.getURL(),
					   getRequiredAttribute(ruleNode,
								HREF)),
				   0,
				   sheetLoadContext,
				   nameTable).getChildren().next();
      if (XSL_NAMESPACE.equals(sheetNode.getName().getNamespace())) {
	if (!XSL_STYLESHEET.equals(sheetNode.getName())
	    && !XSL_TRANSFORM.equals(sheetNode.getName()))
	  throw new XSLException("bad document element for stylesheet",
				 sheetNode);
	parseTopLevel(sheetNode);
      }
      else
	parseRootTemplate(sheetNode);
    }
  }

  private
  class ImportParser extends IncludeParser {
    public void parse(Node ruleNode) throws XSLException, IOException {
      Importance oldFirstImportImportance = firstImportImportance;
      firstImportImportance = currentImportance;
      super.parse(ruleNode);
      currentImportance = currentImportance.createHigher();
      firstImportImportance = oldFirstImportImportance;
    }
  }

  private
  class TemplateParser implements TopLevelParser {
    public void parse(Node defNode) throws XSLException {
      String name = defNode.getAttributeValue(NAME);
      Action contents = parseActions(defNode, emptyAction);
      if (name != null)
        namedTemplateTable.put(expandSourceElementTypeName(name, defNode), contents);
      String pattern = defNode.getAttributeValue(MATCH);
      if (pattern == null)
	return;
      String modeString = defNode.getAttributeValue(MODE);
      TemplateRuleSet ruleSet;
      if (modeString != null)
        ruleSet = getModeTemplateRuleSet(expandSourceElementTypeName(modeString, defNode));
      else
        ruleSet = templateRules;
      try {
        ruleSet.add(ExprParser.parsePattern(defNode, pattern),
	            currentImportance,
		    firstImportImportance,
		    Priority.create(defNode.getAttributeValue(PRIORITY)),
		    contents);
      }
      catch (NumberFormatException e) {
        throw new XSLException("invalid priority", defNode);
      }
    }
  }

  private
  class AttributeSetParser implements TopLevelParser {
    public void parse(Node defNode) throws XSLException {
      Name name = expandSourceElementTypeName(getRequiredAttribute(defNode, NAME), defNode);
      AppendAction action = (AppendAction)attributeSetTable.get(name);
      if (action == null) {
	action = new AppendAction();
	attributeSetTable.put(name, action);
      }
      parseUseAttributeSets(defNode, false, action);
      for (SafeNodeIterator iter = defNode.getChildren();;) {
	Node node = iter.next();
	if (node == null)
	  break;
	if (!XSL_ATTRIBUTE.equals(node.getName()))
	  throw new XSLException("only xsl:attribute allowed inside xsl:attribute-set", node);
      }
      parseActions(defNode, null, action);
    }
  }

  private
  class KeyParser implements TopLevelParser {
    public void parse(Node defNode) throws XSLException {
      // FIXME
    }
  }

  private
  class DecimalFormatParser implements TopLevelParser {
    public void parse(Node defNode) throws XSLException {
      // FIXME
    }
  }

  private
  class VariableTopLevelParser implements TopLevelParser {
    public void parse(Node defNode) throws XSLException {
      parse(defNode, false);
    }

    void parse(Node defNode, boolean isParam) throws XSLException {
      variableInfoTable
	.put(expandSourceElementTypeName(getRequiredAttribute(defNode, NAME), defNode),
	     new VariableInfo(getVariantExpr(defNode),
			      isParam));
    }
  }

  private
  class ParamTopLevelParser extends VariableTopLevelParser {
    public void parse(Node defNode) throws XSLException {
      parse(defNode, true);
    }
  }

  private
  class StripSpaceParser implements TopLevelParser {
    public void parse(Node node) throws XSLException {
      parse(node, true);
    }

    void parse(Node node, boolean strip) throws XSLException {
      StringTokenizer iter
	  = new StringTokenizer(getRequiredAttribute(node, ELEMENTS));
      ImportantBoolean ib = new ImportantBoolean(strip, currentImportance);
      while (iter.hasMoreElements()) {
	String str = (String)iter.nextElement();
	Name name = expandSourceElementTypeName(str, node);
	if (name.getLocalPart().equals("*")) {
	  String ns = name.getNamespace();
	  if (ns == null)
	    stripSource = ib;
	  else {
	    if (stripSourceNamespaceTable == null)
	      stripSourceNamespaceTable = new Hashtable();
	    stripSourceNamespaceTable.put(ns, ib);
	  }
	}
	else {
	  if (stripSourceElementTable == null)
	    stripSourceElementTable = new Hashtable();
	  stripSourceElementTable.put(name, ib);
	}
      }
    }
  }

  private
  class PreserveSpaceParser extends StripSpaceParser {
    public void parse(Node node) throws XSLException {
      parse(node, false);
    }
  }

  private
  class OutputParser implements TopLevelParser {
    public void parse(Node node) throws XSLException {
      outputMethod.merge(node);
    }
  }

  private
  class NamespaceAliasParser implements TopLevelParser {
    public void parse(Node node) throws XSLException {
      namespaceAliasTable.put(getPrefixNamespace(node,
						 getRequiredAttribute(node,
								      STYLESHEET_PREFIX)),
			      getPrefixNamespace(node,
						 getRequiredAttribute(node,
								      RESULT_PREFIX)));
    }
  }

  private
  interface ActionParser {
    Action parse(Node node) throws XSLException;
  }

  private
  class ApplyTemplatesParser implements ActionParser {
    public Action parse(Node node) throws XSLException {
      NodeSetExpr expr = getSortNodeSetExpr(node, getNodeSetExpr(node));
      String modeString = node.getAttributeValue(MODE);
      Name modeName = null;
      if (modeString != null)
	modeName = expandSourceElementTypeName(modeString, node);
      return addParams(new ProcessAction(expr, modeName), node);
    }


    NodeSetExpr getNodeSetExpr(Node node) throws XSLException {
      String select = node.getAttributeValue(SELECT);
      if (select == null)
	return childrenExpr;
      return ExprParser.parseNodeSetExpr(node, select, currentLocalVariables);
    }

  }

  private
  class ForEachParser implements ActionParser {
    public Action parse(Node node) throws XSLException {
      return new ForEachAction(getSortNodeSetExpr(node,
					    ExprParser
					    .parseNodeSetExpr(node, getRequiredAttribute(node, SELECT), currentLocalVariables)),
			       parseActions(node, emptyAction));
    }
  }

  private
  class IfParser implements ActionParser {
    public Action parse(Node node) throws XSLException {
      return new IfAction(makeCondition(node),
			  parseActions(node, emptyAction),
			  emptyAction);
    }

    BooleanExpr makeCondition(Node node) throws XSLException {
      return ExprParser.parseBooleanExpr(node,
					 getRequiredAttribute(node, TEST),
					 currentLocalVariables);
    }
  }

  private
  class CopyParser implements ActionParser {
    public Action parse(Node node) throws XSLException {
      return new CopyAction(parseUseAttributeSetsAndActions(node));
    }
  }

  private
  class CopyOfParser implements ActionParser {
    public Action parse(Node node) throws XSLException {
      return new CopyOfAction(ExprParser.parseVariantExpr(node,
							  getRequiredAttribute(node, SELECT),
							  currentLocalVariables));
    }
  }

  private
  class CommentParser implements ActionParser {
    public Action parse(Node node) throws XSLException {
      return new CommentAction(parseActions(node, emptyAction));
    }
  }

  private
  class ProcessingInstructionParser implements ActionParser {
    public Action parse(Node node) throws XSLException {
      return new ProcessingInstructionAction(
	ExprParser.parseAttributeValueTemplate(node,
					       getRequiredAttribute(node, NAME),
					       currentLocalVariables),
	parseActions(node, emptyAction));
    }
  }

  private
  class ElementParser implements ActionParser {
    public Action parse(Node node) throws XSLException {
      return new ElementAction(
	ExprParser.parseAttributeValueTemplate(node,
					       getRequiredAttribute(node, NAME),
					       currentLocalVariables),
	getNamespaceExpr(node),
	node.getNamespacePrefixMap(),
	parseUseAttributeSetsAndActions(node));
    }

    StringExpr getNamespaceExpr(Node node) throws XSLException {
      String namespace = node.getAttributeValue(NAMESPACE);
      if (namespace == null)
	return null;
      return ExprParser.parseAttributeValueTemplate(node, namespace, currentLocalVariables);
    }
  }

  private
  class AttributeParser extends ElementParser {
    public Action parse(Node node) throws XSLException {
      return new AttributeAction(
	ExprParser.parseAttributeValueTemplate(node,
					       getRequiredAttribute(node, NAME),
					       currentLocalVariables),
	getNamespaceExpr(node),
	node.getNamespacePrefixMap(),
	parseActions(node, emptyAction));
    }
  }

  private
  class DocumentParser implements ActionParser {
    public Action parse(Node node) throws XSLException {
      return new DocumentAction(
	ExprParser.parseAttributeValueTemplate(node,
					       getRequiredAttribute(node, HREF),
					       currentLocalVariables),
	outputMethod.mergeCopy(node),
	parseActions(node, emptyAction));
    }
  }

  private
  class CallTemplateParser implements ActionParser {
    public Action parse(Node node) throws XSLException {
      
      return addParams(new InvokeAction(expandSourceElementTypeName(getRequiredAttribute(node, NAME), node),
					namedTemplateTable),
		       node);
    }
  }

  private
  class ChooseParser extends IfParser {
    public Action parse(Node node) throws XSLException {
      return parseChoices(node.getChildren());
    }
    public Action parseChoices(NodeIterator iter) throws XSLException {
      Node node = iter.next();
      if (node == null)
	return emptyAction;
      Name name = node.getName();
      if (XSL_WHEN.equals(name))
	return new IfAction(makeCondition(node),
			    parseActions(node, emptyAction),
			    parseChoices(iter));
      if (XSL_OTHERWISE.equals(name)) {
	Node next = iter.next();
	if (next != null)
	  throw new XSLException("unexpected element after otherwise",
				 next);
	return parseActions(node, emptyAction);
      }
      throw new XSLException("expected when or otherwise", node);
    }
  }

  private
  class TextParser implements ActionParser {
    public Action parse(Node node) throws XSLException {
      Node child = node.getChildren().next();
      if (child != null) {
	String data = child.getData();
	if (data == null || child.getFollowingSiblings().next() != null)
	  throw new XSLException("xsl:text must not contain elements", node);
	if ("yes".equals(node.getAttributeValue(DISABLE_OUTPUT_ESCAPING)))
	  return new RawCharsAction(data);
	return new CharsAction(data);
      }
      else
	return emptyAction;
    }
  }

  private
  class ValueOfParser implements ActionParser {
    public Action parse(Node node) throws XSLException {
      StringExpr expr = ExprParser.parseStringExpr(node,
						   getRequiredAttribute(node, SELECT),
						   currentLocalVariables);
      if ("yes".equals(node.getAttributeValue(DISABLE_OUTPUT_ESCAPING)))
	return new RawValueOfAction(expr);
      return new ValueOfAction(expr);
    }
  }

  private
  class NumberParser implements ActionParser {
    public Action parse(Node node) throws XSLException {
      NumberListFormatTemplate format = getNumberListFormatTemplate(node);
      String value = node.getAttributeValue(VALUE);
      if (value != null)
	return new ExprNumberAction(ExprParser.parseNumberExpr(node, value, currentLocalVariables), format);
      String level = getOptionalAttribute(node, LEVEL, "single");
      String countString = node.getAttributeValue(COUNT);
      Pattern count;
      if (countString == null)
	count = null;
      else
	count = ExprParser.parsePattern(node, countString, currentLocalVariables);
      String fromString = node.getAttributeValue(FROM);
      Pattern from;
      if (fromString == null)
	from = null;
      else
	from = ExprParser.parsePattern(node, fromString, currentLocalVariables);
      if (level.equals("any"))
	return new AnyLevelNumberAction(count, from, format);
      if (level.equals("multiple"))
	return new MultiLevelNumberAction(count, from, format);
      if (level.equals("single"))
	return new SingleLevelNumberAction(count, from, format);
      throw new XSLException("bad level", node);
    }
  }

  private
  class ApplyImportsParser implements ActionParser {
    public Action parse(Node node) throws XSLException {
      return new ApplyImportsAction();
    }
  }

  private
  class VariableActionParser implements ActionParser {
    public Action parse(Node node) throws XSLException {
      Name name = expandSourceElementTypeName(getRequiredAttribute(node, NAME), node);
      VariantExpr expr = getVariantExpr(node);
      // Must create the VariantExpr before adding to local variables.
      currentLocalVariables = new AddVariableSet(name, currentLocalVariables);
      nCurrentLocalVariables++;
      return makeAction(name, expr);
    }

    Action makeAction(Name name, VariantExpr expr) {
      return new BindLocalVariableAction(name, expr);
    }
  }

  private
  class ParamActionParser extends VariableActionParser {
    Action makeAction(Name name, VariantExpr expr) {
      return new BindLocalParamAction(name, expr);
    }
  }

  private
  class MessageParser implements ActionParser {
    public Action parse(Node node) throws XSLException {
      boolean terminate = "yes".equals(node.getAttributeValue(TERMINATE));
      Action content = parseActions(node, emptyAction);
      if (terminate)
	return new TerminateMessageAction(content);
      else
	return new MessageAction(content);
    }
  }

  private
  class OutputMethodImpl implements OutputMethod, Cloneable {
    private Name outputMethodName = null;
    private Vector outputMethodAttributes = new Vector();
    private Vector outputCdataSectionElements = new Vector();

    OutputMethod mergeCopy(Node node) throws XSLException {
      SafeNodeIterator iter = node.getAttributes();
      if (iter.next() != null && iter.next() == null)
	return this;
      OutputMethodImpl om = (OutputMethodImpl)clone();
      om.merge(node);
      return om;
    }

    public Object clone() {
      try {
	OutputMethodImpl om = (OutputMethodImpl)super.clone();
	om.outputCdataSectionElements = (Vector)outputCdataSectionElements.clone();
	om.outputMethodAttributes = (Vector)outputMethodAttributes.clone();
	return om;
      }
      catch (CloneNotSupportedException e) {
	throw new Error("unexpected CloneNotSupportedException");
      }
    }

    void merge(Node node) throws XSLException {
      String nameString = node.getAttributeValue(METHOD);
      if (nameString != null)
	outputMethodName = node.getNamespacePrefixMap().expandAttributeName(nameString, node);
      // FIXME error checking
      for (SafeNodeIterator iter = node.getAttributes();;) {
	Node att = iter.next();
	if (att == null)
	  break;
	Name name = att.getName();
	String value = att.getData();
	int i = outputMethodAttributes.indexOf(name);
	// FIXME won't work for cdata-section-elements
	if (i >= 0)
	  outputMethodAttributes.setElementAt(att.getData(), i + 1);
	else if (name.equals(CDATA_SECTION_ELEMENTS)) {
	  StringTokenizer t = new StringTokenizer(value);
	  while (t.hasMoreElements())
	    outputCdataSectionElements.addElement(node
						  .getNamespacePrefixMap()
						  .expandAttributeName((String)t.nextElement(), node));
	}
	else if (!name.equals(METHOD) && !name.equals(HREF)) {
	  outputMethodAttributes.addElement(name);
	  outputMethodAttributes.addElement(value);
	}
      }
    }

    public Name getName() {
      return outputMethodName;
    }

    public String getAttributeValue(Name name) {
      int len = outputMethodAttributes.size();
      for (int i = 0; i < len; i += 2)
	if (name.equals(outputMethodAttributes.elementAt(i)))
	  return (String)outputMethodAttributes.elementAt(i + 1);
      return null;
    }

    public Name[] getAttributeNames() {
      Name names[] = new Name[outputMethodAttributes.size()/2];
      for (int i = 0; i < names.length; i++)
	names[i] = (Name)outputMethodAttributes.elementAt(i*2);
      return names;
    }

    public Name[] getCdataSectionElements() {
      Name names[] = new Name[outputCdataSectionElements.size()];
      for (int i = 0; i < names.length; i++)
	names[i] = (Name)outputCdataSectionElements.elementAt(i);
      return names;
    }

    public NameTable getNameTable() {
      return nameTable;
    }

  }

  private ImportantBoolean stripSource = null;
  private Hashtable stripSourceElementTable = null;
  private Hashtable stripSourceNamespaceTable = null;
  private TemplateRuleSet templateRules = new TemplateRuleSet(new BuiltinAction());
  private Hashtable modeTable = new Hashtable();
  private Hashtable namedTemplateTable = new Hashtable();
  private Hashtable variableInfoTable = new Hashtable();
  private Hashtable attributeSetTable = new Hashtable();
  private Hashtable namespaceAliasTable = new Hashtable();
  private Vector idAttributes = new Vector();
  private static final Action emptyAction = new EmptyAction();
  private Hashtable topLevelTable = new Hashtable();
  private Hashtable actionTable = new Hashtable();
  Importance currentImportance = Importance.create();
  Importance firstImportImportance = Importance.create();
  static NodeSetExpr childrenExpr = ExprParser.getChildrenExpr();
  VariableSet currentLocalVariables = new EmptyVariableSet();
  int nCurrentLocalVariables = 0;
  Vector excludedNamespaces;
  XMLProcessor parser;
  static StringVariant emptyStringVariant = new StringVariant("");
  Name XSL_WHEN;
  Name XSL_OTHERWISE;
  Name XSL_STYLESHEET;
  Name XSL_TRANSFORM;
  Name XSL_WITH_PARAM;
  Name XSL_SORT;
  Name XSL_FOR_EACH;
  Name XSL_FALLBACK;
  Name XSL_VERSION;
  Name XSL_VENDOR;
  Name XSL_VENDOR_URL;
  Name XSL_USE_ATTRIBUTE_SETS;
  Name XSL_ATTRIBUTE;
  Name HREF;
  Name MATCH;
  Name PRIORITY;
  Name SELECT;
  Name TEST;
  Name METHOD;
  Name CDATA_SECTION_ELEMENTS;
  Name NAME;
  Name NAMESPACE;
  Name DEFAULT;
  Name VALUE;
  Name ELEMENTS;
  Name ATTRIBUTE;
  Name FROM;
  Name COUNT;
  Name LEVEL;
  Name FORMAT;
  Name LETTER_VALUE;
  Name GROUPING_SIZE;
  Name GROUPING_SEPARATOR;
  Name MODE;
  Name ORDER;
  Name LANG;
  Name CASE_ORDER;
  Name DATA_TYPE;
  Name DISABLE_OUTPUT_ESCAPING;
  Name USE_ATTRIBUTE_SETS;
  Name EXCLUDE_RESULT_PREFIXES;
  Name RESULT_PREFIX;
  Name STYLESHEET_PREFIX;
  Name TERMINATE;
  LoadContext sheetLoadContext;
  NameTable nameTable;
  ExtensionHandler extensionHandler;
  OutputMethodImpl outputMethod = new OutputMethodImpl();
  
  SheetImpl(Node node, XMLProcessor parser, ExtensionHandler extensionHandler,
	    LoadContext sheetLoadContext, NameTable nameTable)
    throws IOException, XSLException {
    this.sheetLoadContext = sheetLoadContext;
    this.nameTable = nameTable;
    this.parser = parser;
    this.extensionHandler = extensionHandler;
    XSL_WHEN = xsl("when");
    XSL_OTHERWISE = xsl("otherwise");
    XSL_STYLESHEET = xsl("stylesheet");
    XSL_TRANSFORM = xsl("transform");
    XSL_WITH_PARAM = xsl("with-param");
    XSL_SORT = xsl("sort");
    XSL_FOR_EACH = xsl("for-each");
    XSL_FALLBACK = xsl("fallback");
    XSL_VERSION = xsl("version");
    XSL_VENDOR = xsl("vendor");
    XSL_VENDOR_URL = xsl("vendor-url");
    XSL_USE_ATTRIBUTE_SETS = xsl("use-attribute-sets");
    XSL_ATTRIBUTE = xsl("attribute");
    HREF = nameTable.createName("href");
    SELECT = nameTable.createName("select");
    TEST = nameTable.createName("test");
    MATCH = nameTable.createName("match");
    PRIORITY = nameTable.createName("priority");
    METHOD = nameTable.createName("method");
    CDATA_SECTION_ELEMENTS = nameTable.createName("cdata-section-elements");
    NAME = nameTable.createName("name");
    NAMESPACE = nameTable.createName("namespace");
    DEFAULT = nameTable.createName("default");
    VALUE = nameTable.createName("value");
    ELEMENTS = nameTable.createName("elements");
    ATTRIBUTE = nameTable.createName("attribute");
    COUNT = nameTable.createName("count");
    LEVEL = nameTable.createName("level");
    FROM = nameTable.createName("from");
    FORMAT = nameTable.createName("format");
    LETTER_VALUE = nameTable.createName("letter-value");
    GROUPING_SIZE = nameTable.createName("grouping-size");
    GROUPING_SEPARATOR = nameTable.createName("grouping-separator");
    MODE = nameTable.createName("mode");
    ORDER = nameTable.createName("order");
    LANG = nameTable.createName("lang");
    DATA_TYPE = nameTable.createName("data-type");
    CASE_ORDER = nameTable.createName("case-order");
    DISABLE_OUTPUT_ESCAPING = nameTable.createName("disable-output-escaping");
    USE_ATTRIBUTE_SETS = nameTable.createName("use-attribute-sets");
    EXCLUDE_RESULT_PREFIXES = nameTable.createName("exclude-result-prefixes");
    STYLESHEET_PREFIX = nameTable.createName("stylesheet-prefix");
    RESULT_PREFIX = nameTable.createName("result-prefix");
    TERMINATE = nameTable.createName("terminate");
    
    topLevelTable.put(xsl("include"), new IncludeParser());
    topLevelTable.put(xsl("import"), new ImportParser());
    topLevelTable.put(xsl("template"), new TemplateParser());
    topLevelTable.put(xsl("attribute-set"), new AttributeSetParser());
    topLevelTable.put(xsl("decimal-format"), new DecimalFormatParser());
    topLevelTable.put(xsl("key"), new KeyParser());
    topLevelTable.put(xsl("variable"), new VariableTopLevelParser());
    topLevelTable.put(xsl("param"), new ParamTopLevelParser());
    topLevelTable.put(xsl("strip-space"), new StripSpaceParser());
    topLevelTable.put(xsl("preserve-space"), new PreserveSpaceParser());
    topLevelTable.put(xsl("output"), new OutputParser());
    topLevelTable.put(xsl("namespace-alias"), new NamespaceAliasParser());
    actionTable.put(xsl("text"), new TextParser());
    actionTable.put(xsl("apply-templates"), new ApplyTemplatesParser());
    actionTable.put(xsl("call-template"), new CallTemplateParser());
    actionTable.put(xsl("for-each"), new ForEachParser());
    actionTable.put(xsl("value-of"), new ValueOfParser());
    actionTable.put(xsl("number"), new NumberParser());
    actionTable.put(xsl("if"), new IfParser());
    actionTable.put(xsl("choose"), new ChooseParser());
    actionTable.put(xsl("copy"), new CopyParser());
    actionTable.put(xsl("copy-of"), new CopyOfParser());
    actionTable.put(xsl("comment"), new CommentParser());
    actionTable.put(xsl("processing-instruction"), new ProcessingInstructionParser());
    actionTable.put(xsl("element"), new ElementParser());
    actionTable.put(xsl("attribute"), new AttributeParser());
    actionTable.put(xsl("apply-imports"), new ApplyImportsParser());
    actionTable.put(xsl("variable"), new VariableActionParser());
    actionTable.put(xsl("param"), new ParamActionParser());
    actionTable.put(xsl("message"), new MessageParser());
    actionTable.put(xt("document"), new DocumentParser());
    parseSheet(node);
  }

  public
  Result process(Node node, XMLProcessor parser, ParameterSet params, Result root) throws XSLException {
    root.start(outputMethod);
    new ProcessContextImpl(this, node, parser, params).processSafe(node, null, root);
    root.end();
    return root;
  }

  private
  void parseSheet(Node rootNode) throws XSLException, IOException {
    Node sheetNode = rootNode.getChildren().next();
    if (XSL_NAMESPACE.equals(sheetNode.getName().getNamespace())) {
      if (!XSL_STYLESHEET.equals(sheetNode.getName())
	  && !XSL_TRANSFORM.equals(sheetNode.getName()))
	throw new XSLException("bad document element for stylesheet",
			       sheetNode);
      parseTopLevel(sheetNode);
    }
    else
      parseRootTemplate(rootNode);
    templateRules.compile();
    for (Enumeration iter = modeTable.elements(); iter.hasMoreElements();)
      ((TemplateRuleSet)iter.nextElement()).compile();
  }

  private Vector getExcludedNamespaces(Node node) throws XSLException {
    String prefixList = node.getAttributeValue(EXCLUDE_RESULT_PREFIXES);
    if (prefixList == null)
      return null;
    Vector v = new Vector();
    StringTokenizer iter = new StringTokenizer(prefixList);
    while (iter.hasMoreElements()) {
      String prefix = (String)iter.nextElement();
      if (prefix == null)
	break;
      v.addElement(getPrefixNamespace(node, prefix));
    }
    if (v.size() == 0)
      return null;
    return v;
  }

  String getPrefixNamespace(Node node, String prefix) throws XSLException {
    NamespacePrefixMap map = node.getNamespacePrefixMap();
    String ns = map.getNamespace(prefix);
    if (ns == null) {
      if (prefix.equals("#default"))
	ns = map.getDefaultNamespace();
      if (ns == null)
	throw new XSLException("undefined prefix", node);
    }
    return ns;
  }

  void parseTopLevel(Node sheetNode) throws XSLException, IOException {
    Vector saveExcludedNamespaces = excludedNamespaces;
    excludedNamespaces = getExcludedNamespaces(sheetNode);
    for (NodeIterator iter = sheetNode.getChildren();;) {
      Node node = iter.next();
      if (node == null)
	break;
      TopLevelParser parser = null;
      try {
	Name name = node.getName();
	if (name == null)
	  throw new XSLException("illegal data characters inside xsl:stylesheet", node);
	parser = (TopLevelParser)topLevelTable.get(name);
	if (parser == null && name.getNamespace() == null)
	  throw new XSLException("illegal top-level element", node);
      }
      catch (ClassCastException e) { }
      if (parser != null)
	parser.parse(node);
    }
    excludedNamespaces = saveExcludedNamespaces;
  }

  void parseRootTemplate(Node defNode) throws XSLException {
    templateRules.add(ExprParser.parsePattern(defNode, "/"),
		      currentImportance,
		      firstImportImportance,
		      null,
		      parseActions(defNode, emptyAction));
  }

  /* Parse the attributes on node as literal attributes and then
     parse the actions. */

  Action parseAttributesAndActions(Node node) throws XSLException {
    AppendAction sequence = parseUseAttributeSets(node, true, null);
    for (NodeIterator iter = node.getAttributes();;) {
      Node att = iter.next();
      if (att == null)
	break;
      if (sequence == null)
	sequence = new AppendAction();
      String value = att.getData();
      Name name = att.getName();
      if (XSL_NAMESPACE.equals(name.getNamespace()))
	continue; // FIXME error checking
      if (value.indexOf('{') >= 0 || value.indexOf('}') >= 0)
	sequence.add(new TemplateAttributeAction(name,
						 ExprParser.parseAttributeValueTemplate(node, value, currentLocalVariables)));
      else
	sequence.add(new LiteralAttributeAction(name, value));
    }
    return parseActions(node, null, sequence);
  }

  Action parseUseAttributeSetsAndActions(Node node) throws XSLException {
    return parseActions(node,
			emptyAction,
			parseUseAttributeSets(node, false, null));
  }

  AppendAction parseUseAttributeSets(Node node, boolean literal, AppendAction sequence) throws XSLException {
    String value = node.getAttributeValue(literal
					  ? XSL_USE_ATTRIBUTE_SETS
					  : USE_ATTRIBUTE_SETS);
    if (value != null) {
      for (StringTokenizer iter = new StringTokenizer(value);
	   iter.hasMoreElements();) {
	if (sequence == null)
	  sequence = new AppendAction();
	sequence.add(new UseAttributeSetAction(expandSourceElementTypeName((String)iter.nextElement(),node)));
      }
    }
    return sequence;
  }

  Action parseActions(Node node, Action ifEmpty) throws XSLException {
    return parseActions(node, ifEmpty, null);
  }
  
  Action parseActions(Node node, Action ifEmpty, AppendAction sequence) throws XSLException {
    final VariableSet startLocalVariables = currentLocalVariables;
    final int nStartLocalVariables = nCurrentLocalVariables;
    NodeIterator iter = node.getChildren();
    node = iter.next();
    if (node == null) {
      if (sequence == null)
	return ifEmpty;
      else
	return sequence;
    }
    if (sequence == null)
      sequence = new AppendAction();
    do {
      switch (node.getType()) {
      case Node.TEXT:
	sequence.add(new CharsAction(node.getData()));
	break;
      case Node.ELEMENT:
	{
	  Name name = node.getName();
	  String ns = name.getNamespace();
	  if (!XSL_NAMESPACE.equals(ns) && !XT_NAMESPACE.equals(ns))
	    sequence.add(new LiteralElementAction(node.getName(),
					          literalNamespacePrefixMap(node),
					          parseAttributesAndActions(node)));
	  else {
	    ActionParser actionParser = null;
	    try {
	      actionParser = (ActionParser)actionTable.get(name);
	    }
	    catch (ClassCastException e) { }
	    if (actionParser == null) {
	      if (name.equals(XSL_SORT)
		  && XSL_FOR_EACH.equals(node.getParent().getName()))
		;
	      else if (name.equals(XSL_FALLBACK))
		; // FIXME error checking
	      else
		throw new XSLException("expected action not " + name, node);
	    }
	    else
	      sequence.add(actionParser.parse(node));
	  }
	}
      }
      node = iter.next();
    } while (node != null);
    // FIXME should use finally here
    if (nStartLocalVariables != nCurrentLocalVariables) {
      sequence.add(new UnbindLocalVariablesAction(nCurrentLocalVariables - nStartLocalVariables));
      nCurrentLocalVariables = nStartLocalVariables;
      currentLocalVariables = startLocalVariables;
    }
    return sequence;
  }

  String getRequiredAttribute(Node node, Name name) throws XSLException {
    String value = node.getAttributeValue(name);
    if (value == null)
      throw new XSLException("missing attribute \"" + name + "\"", node);
    return value;
  }

  String getOptionalAttribute(Node node, Name name, String dflt) {
    String value = node.getAttributeValue(name);
    return value == null ? dflt : value;
  }

  String getData(Node node) throws XSLException {
    node = node.getChildren().next();
    if (node == null)
      return "";
    String data = node.getData();
    if (data == null)
      throw new XSLException("only character data allowed", node);
    return data;
  }

  NumberListFormatTemplate getNumberListFormatTemplate(Node node) throws XSLException {
    NumberListFormatTemplate t = new NumberListFormatTemplate();
    String value = node.getAttributeValue(FORMAT);
    if (value != null) {
      StringExpr expr = ExprParser.parseAttributeValueTemplate(node, value, currentLocalVariables);
      String format = expr.constantValue();
      if (format != null)
  	t.setFormat(format);
      else
	t.setFormat(expr);
    }
    // FIXME should be able to use attribute value templates for these
    value = node.getAttributeValue(LANG);
    if (value != null)
      t.setLang(value);
    value = node.getAttributeValue(LETTER_VALUE);
    if (value != null)
      t.setLetterValue(value);
    value = node.getAttributeValue(GROUPING_SIZE);
    if (value != null) {
      try {
	t.setGroupingSize(Integer.parseInt(value));
      }
      catch (NumberFormatException e) { }
    }
    value = node.getAttributeValue(GROUPING_SEPARATOR);
    if (value != null)
      t.setGroupingSeparator(value);
    return t;
  }

  Action addParams(ParamAction action, Node node) throws XSLException {
    for (NodeIterator iter = node.getChildren();;) {
      node = iter.next();
      if (node == null)
	break;
      if (XSL_WITH_PARAM.equals(node.getName()))
	action.addParam(expandSourceElementTypeName(getRequiredAttribute(node, NAME), node),
			getVariantExpr(node));
    }
    return action;
  }

  NodeSetExpr getSortNodeSetExpr(Node node, NodeSetExpr expr) throws XSLException {
    ComparatorTemplate comparatorTemplate = null;
    for (NodeIterator iter = node.getChildren();;) {
      node = iter.next();
      if (node == null)
	break;
      if (XSL_SORT.equals(node.getName())) {
	Locale locale = Lang.getLocale(node.getAttributeValue(LANG));
	Comparator cmp;
	String dataType = node.getAttributeValue(DATA_TYPE);
	if ("number".equals(dataType))
	  cmp = new NumberComparator();
	else {
	  int caseOrder = 0;
	  String caseOrderString = node.getAttributeValue(CASE_ORDER);
	  if ("upper-first".equals(caseOrderString))
	    caseOrder = TextComparator.UPPER_FIRST;
	  else if ("lower-first".equals(caseOrderString))
	    caseOrder = TextComparator.LOWER_FIRST;
	  cmp = TextComparator.create(locale, caseOrder);
	}
	if ("descending".equals(node.getAttributeValue(ORDER)))
	  cmp = new ReverseComparator(cmp);
	ComparatorTemplate templ 
	  = new NodeComparatorTemplate(cmp,
				       ExprParser.parseStringExpr(node,
								  getOptionalAttribute(node, SELECT, "."), currentLocalVariables));
	if (comparatorTemplate == null)
	  comparatorTemplate = templ;
	else
	  comparatorTemplate = new BilevelComparatorTemplate(comparatorTemplate, templ);
      }
    }
    if (comparatorTemplate == null)
      return expr;
    return new SortNodeSetExpr(expr, comparatorTemplate);
  }

  VariantExpr getVariantExpr(Node defNode) throws XSLException {
    String expr = defNode.getAttributeValue(SELECT);
    if (defNode.getChildren().next() == null) {
      if (expr == null)
	expr = "\"\"";
    }
    else {
      if (expr != null)
	throw new XSLException("non-empty content with select attribute", defNode);
      // OPT optimize case when there's a single text node child;
      // optimize case when the children consist of text nodes
      // and xsl:value-of elements
      return new ResultFragmentExpr(parseActions(defNode, emptyAction),
				    defNode,
				    extensionHandler);
    }
    return ExprParser.parseVariantExpr(defNode, expr, currentLocalVariables);
  }

  public TemplateRuleSet getModeTemplateRuleSet(Name modeName) {
    if (modeName == null)
      return templateRules;
    TemplateRuleSet ruleSet = (TemplateRuleSet)modeTable.get(modeName);
    if (ruleSet == null) {
      ruleSet = new TemplateRuleSet(new BuiltinAction(modeName));
      modeTable.put(modeName, ruleSet);
    }
    return ruleSet;
  }

  static final String XSL_NAMESPACE = "http://www.w3.org/1999/XSL/Transform";
  static final String XT_NAMESPACE = "http://www.jclark.com/xt";

  private Name xsl(String name) {
    return nameTable.createName("xsl:" + name, XSL_NAMESPACE);
  }
  
  private Name xt(String name) {
    return nameTable.createName("xt:" + name, XT_NAMESPACE);
  }
  
  private boolean namespaceExcluded(String ns) {
    if (ns == null)
      return false;
    if (ns.equals(XSL_NAMESPACE) || ns.equals(XT_NAMESPACE))
      return true;
    if (excludedNamespaces == null)
      return false;
    int len = excludedNamespaces.size();
    for (int i = 0; i < len; i++)
      if (excludedNamespaces.elementAt(i).equals(ns))
	return true;
    return false;
  }

  private NamespacePrefixMap literalNamespacePrefixMap(Node node) {
    NamespacePrefixMap map = node.getNamespacePrefixMap();
    NamespacePrefixMap newMap = map;
    String ns = map.getDefaultNamespace();
    if (namespaceExcluded(ns))
      newMap = newMap.unbindDefault();
    int size = map.getSize();
    for (int i = 0; i < size; i++) {
      ns = map.getNamespace(i);
      if (namespaceExcluded(ns))
	newMap = newMap.unbind(map.getPrefix(i));
    }
    return newMap;
  }

  static Name expandSourceElementTypeName(String nameString, Node node) throws XSLException {
    return node.getNamespacePrefixMap().expandAttributeName(nameString, node);
  }

  public LoadContext getSourceLoadContext() {
    return this;
  }

  public boolean getStripSource(Name elementTypeName) {
    ImportantBoolean match = stripSource;
    if (stripSourceNamespaceTable != null) {
      String ns = elementTypeName.getNamespace();
      if (ns != null) {
	ImportantBoolean ib = (ImportantBoolean)stripSourceNamespaceTable.get(ns);
	if (ib != null
	    && (match == null
		|| ib.getImportance().compareTo(match.getImportance()) >= 0))
	  match = ib;
      }
    }
    if (stripSourceElementTable != null) {
      ImportantBoolean ib = (ImportantBoolean)stripSourceElementTable.get(elementTypeName);
      if (ib != null
	  && (match == null
	      || ib.getImportance().compareTo(match.getImportance()) >= 0))
	match = ib;
    }
    return match != null ? match.getBoolean() : false;
  }

  public boolean getIncludeComments() {
    return true;
  }

  public boolean getIncludeProcessingInstructions() {
    return true;
  }

  public VariableInfo getGlobalVariableInfo(Name name) {
    return (VariableInfo)variableInfoTable.get(name);
  }

  public Variant getSystemProperty(Name name) {
    if (name.equals(XSL_VERSION))
      return new NumberVariant(1.0);
    if (name.equals(XSL_VENDOR))
      return new StringVariant("James Clark");
    if (name.equals(XSL_VENDOR_URL))
      return new StringVariant("http://www.jclark.com/");
    return emptyStringVariant;
  }

  public ExtensionContext createExtensionContext(String namespace) throws XSLException {
    return extensionHandler.createContext(namespace);
  }

  public Action getAttributeSet(Name name) {
    return (Action)attributeSetTable.get(name);
  }

  public boolean haveNamespaceAliases() {
    return namespaceAliasTable.size() != 0;
  }
  
  public String getNamespaceAlias(String ns) {
    if (ns == null)
      return null;
    return (String)namespaceAliasTable.get(ns);
  }
}
