/**
   * Get a Receiver that wraps a given Result object. Saxon calls this method to construct a
   * serialization pipeline. The method can be overridden in a subclass; alternatively, the subclass
   * can override the various methods used to instantiate components of the serialization pipeline.
   *
   * @param result The final destination of the serialized output. Usually a StreamResult, but other
   *     kinds of Result are possible.
   * @param pipe The PipelineConfiguration.
   * @param props The serialization properties
   */
  public Receiver getReceiver(Result result, PipelineConfiguration pipe, Properties props)
      throws XPathException {
    if (result instanceof Emitter) {
      ((Emitter) result).setOutputProperties(props);
      return (Emitter) result;
    } else if (result instanceof Receiver) {
      Receiver receiver = (Receiver) result;
      receiver.setSystemId(result.getSystemId());
      receiver.setPipelineConfiguration(pipe);
      return receiver;
    } else if (result instanceof SAXResult) {
      ContentHandlerProxy proxy = newContentHandlerProxy();
      proxy.setUnderlyingContentHandler(((SAXResult) result).getHandler());
      proxy.setPipelineConfiguration(pipe);
      proxy.setOutputProperties(props);
      if ("yes".equals(props.getProperty(SaxonOutputKeys.SUPPLY_SOURCE_LOCATOR))) {
        if (pipe.getConfiguration().isCompileWithTracing()) {
          pipe.getController().addTraceListener(proxy.getTraceListener());
        } else {
          DynamicError de =
              new DynamicError(
                  "Cannot use saxon:supply-source-locator unless tracing was enabled at compile time");
          de.setErrorCode(SaxonErrorCode.SXSE0002);
          throw de;
        }
      }
      proxy.open();
      return proxy;
    } else if (result instanceof StreamResult) {

      // The "target" is the start of the output pipeline, the Receiver that
      // instructions will actually write to (except that other things like a
      // NamespaceReducer may get added in front of it). The "emitter" is the
      // last thing in the output pipeline, the Receiver that actually generates
      // characters or bytes that are written to the StreamResult.

      Receiver target;
      String method = props.getProperty(OutputKeys.METHOD);
      if (method == null) {
        target = newUncommittedSerializer(result, props);
        target.setPipelineConfiguration(pipe);
        return target;
      }

      Emitter emitter;

      CharacterMapExpander characterMapExpander = null;
      String useMaps = props.getProperty(SaxonOutputKeys.USE_CHARACTER_MAPS);
      if (useMaps != null) {
        Controller controller = (pipe == null ? null : pipe.getController());
        if (controller == null) {
          DynamicError de =
              new DynamicError("Cannot use character maps in an environment with no Controller");
          de.setErrorCode(SaxonErrorCode.SXSE0001);
          throw de;
        }
        characterMapExpander = controller.makeCharacterMapExpander(useMaps, this);
        characterMapExpander.setPipelineConfiguration(pipe);
      }

      ProxyReceiver normalizer = null;
      String normForm = props.getProperty(SaxonOutputKeys.NORMALIZATION_FORM);
      if (normForm != null && !normForm.equals("none")) {
        normalizer = newUnicodeNormalizer(pipe, props);
      }

      if ("html".equals(method)) {
        emitter = newHTMLEmitter();
        emitter.setPipelineConfiguration(pipe);
        target = createHTMLSerializer(emitter, props, pipe, characterMapExpander, normalizer);

      } else if ("xml".equals(method)) {
        emitter = newXMLEmitter();
        emitter.setPipelineConfiguration(pipe);
        target = createXMLSerializer(emitter, props, pipe, normalizer, characterMapExpander);

      } else if ("xhtml".equals(method)) {
        emitter = newXHTMLEmitter();
        emitter.setPipelineConfiguration(pipe);
        target = createXHTMLSerializer(emitter, props, pipe, normalizer, characterMapExpander);

      } else if ("text".equals(method)) {
        emitter = newTEXTEmitter();
        emitter.setPipelineConfiguration(pipe);
        target = createTextSerializer(emitter, characterMapExpander, normalizer);

      } else {
        Receiver userReceiver;
        if (pipe == null) {
          throw new DynamicError("Unsupported serialization method " + method);
        } else {
          // See if this output method is recognized by the Configuration
          userReceiver = pipe.getConfiguration().makeEmitter(method, pipe.getController());
          userReceiver.setPipelineConfiguration(pipe);
          if (userReceiver instanceof ContentHandlerProxy
              && "yes".equals(props.getProperty(SaxonOutputKeys.SUPPLY_SOURCE_LOCATOR))) {
            if (pipe.getConfiguration().isCompileWithTracing()) {
              pipe.getController()
                  .addTraceListener(((ContentHandlerProxy) userReceiver).getTraceListener());
            } else {
              DynamicError de =
                  new DynamicError(
                      "Cannot use saxon:supply-source-locator unless tracing was enabled at compile time");
              de.setErrorCode(SaxonErrorCode.SXSE0002);
              throw de;
            }
          }
          target = userReceiver;
          if (userReceiver instanceof Emitter) {
            emitter = (Emitter) userReceiver;
          } else {
            return userReceiver;
          }
        }
      }
      emitter.setOutputProperties(props);
      StreamResult sr = (StreamResult) result;
      emitter.setStreamResult(sr);
      return target;

    } else {
      if (pipe != null) {
        // try to find an external object model that knows this kind of Result
        List externalObjectModels = pipe.getConfiguration().getExternalObjectModels();
        for (int m = 0; m < externalObjectModels.size(); m++) {
          ExternalObjectModel model = (ExternalObjectModel) externalObjectModels.get(m);
          Receiver builder = model.getDocumentBuilder(result);
          if (builder != null) {
            builder.setSystemId(result.getSystemId());
            builder.setPipelineConfiguration(pipe);
            return builder;
          }
        }
      }
    }

    throw new IllegalArgumentException("Unknown type of result: " + result.getClass());
  }