Index: test/test_stylesheet.rb
===================================================================
--- test/test_stylesheet.rb	(revision 131)
+++ test/test_stylesheet.rb	(working copy)
@@ -105,4 +105,142 @@
       GC.start
     end
   end    
+
+  def test_stylesheet_string
+    filename = File.join(File.dirname(__FILE__), 'files/params.xsl')
+    style = File.open(filename).readline(nil)
+    stylesheet = XSLT::Stylesheet.string(style)
+    assert_instance_of(XSLT::Stylesheet, stylesheet)
+  end
+
+  def test_stylesheet_file
+    filename = File.join(File.dirname(__FILE__), 'files/params.xsl')
+    stylesheet = XSLT::Stylesheet.file(filename)
+    assert_instance_of(XSLT::Stylesheet, stylesheet)
+  end
+
+  def test_stylesheet_io
+    filename = File.join(File.dirname(__FILE__), 'files/params.xsl')
+    stylesheet = XSLT::Stylesheet.io(File.open(filename))
+    assert_instance_of(XSLT::Stylesheet, stylesheet)
+  end
+
+  def test_save_result
+    filename = File.join(File.dirname(__FILE__), 'files/fuzface.xsl')
+    sdoc = XML::Document.file(filename)
+    stylesheet = XSLT::Stylesheet.new(sdoc)
+    
+    rdoc = stylesheet.apply(doc)
+
+    filename = File.join(File.dirname(__FILE__), 'files/fuzface_result.xml')
+    stylesheet.save_result(rdoc, File.open(filename, "w"))
+    xml = File.open(filename).read
+
+    # output method is html -> no xml decl, empty tags not closed...
+    assert xml =~ /^<html>/
+    assert xml =~ /<meta http-equiv="Content-Type" content="text\/html; charset=UTF-8">\n<title>/
+    assert_equal(6034, xml.length)
+  end
+
+  def test_dump_result
+    filename = File.join(File.dirname(__FILE__), 'files/fuzface.xsl')
+    sdoc = XML::Document.file(filename)
+    stylesheet = XSLT::Stylesheet.new(sdoc)
+    
+    rdoc = stylesheet.apply(doc)
+
+    xml = stylesheet.dump_result(rdoc)
+
+    # output method is html -> no xml decl, empty tags not closed...
+    assert xml =~ /^<html>/
+    assert xml =~ /<meta http-equiv="Content-Type" content="text\/html; charset=UTF-8">\n<title>/
+    assert_equal(6034, xml.length)
+  end
+
+  def test_transform_to_file
+    filename = File.join(File.dirname(__FILE__), 'files/fuzface.xsl')
+    sdoc = XML::Document.file(filename)
+    stylesheet = XSLT::Stylesheet.new(sdoc)
+    
+    filename = File.join(File.dirname(__FILE__), 'files/fuzface_result.xml')
+
+    stylesheet.transform_to_file(doc, File.open(filename, "w"))
+    xml = File.open(filename).read
+
+    assert_equal(6034, xml.length)
+  end
+
+  def test_transform_to_string
+    filename = File.join(File.dirname(__FILE__), 'files/fuzface.xsl')
+    sdoc = XML::Document.file(filename)
+    stylesheet = XSLT::Stylesheet.new(sdoc)
+    
+    xml = stylesheet.transform_to_string(doc)
+
+    assert_equal(6034, xml.length)
+  end
+
+  def test_entities
+    style = <<EOF
+<!DOCTYPE xsl:stylesheet [
+<!ENTITY foo 'bar'>
+]>
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+  <xsl:template match="a">
+    <out><xsl:text>&foo;</xsl:text>
+    <xsl:apply-templates/></out>
+  </xsl:template>
+</xsl:stylesheet>
+EOF
+
+    styledoc = LibXML::XML::Parser.string(style, :options => XSLT::Stylesheet::PARSE_OPTIONS).parse  
+    stylesheet = XSLT::Stylesheet.new(styledoc)
+    
+    xml = "<!DOCTYPE a [<!ENTITY bla 'fasel'>]><a>&bla;</a>"
+    doc = XML::Parser.string(xml, :options => XSLT::Stylesheet::PARSE_OPTIONS).parse
+    
+    out = stylesheet.apply( doc )
+    dump = stylesheet.dump_result( out )
+    assert_match( /<out>barfasel<\/out>/, dump)
+
+    # no entity replacement in document
+    doc = XML::Parser.string(xml, :options => 0).parse
+    out = stylesheet.apply( doc )
+    dump = stylesheet.dump_result( out )
+
+    assert_match(/<out>bar<\/out>/, dump) # entity content is missing
+
+    # note: having entities in your stylesheet that are not replaced during
+    # parse, will crash libxslt (segfault)
+    # seems to be a libxslt problem; you should not do that anyway
+    # styledoc = LibXML::XML::Parser.string(style, : options => 0).parse
+    # stylesheet = XSLT::Stylesheet.new(styledoc)
+  end
+
+  def test_cdatasection
+    doc = XML::Parser.string("<a/>").parse
+    
+    style = <<EOF
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+  <xsl:template match="a">
+    <out><xsl:text disable-output-escaping="yes"><![CDATA[<>]]></xsl:text></out>
+  </xsl:template>
+</xsl:stylesheet>
+EOF
+
+    styledoc = LibXML::XML::Parser.string(style, :options => XSLT::Stylesheet::PARSE_OPTIONS).parse 
+    stylesheet = XSLT::Stylesheet.new(styledoc)
+    
+    out = stylesheet.apply( doc )
+    dump = stylesheet.dump_result( out )
+    assert_match( /<out><><\/out>/, dump)
+
+    # without propper parse options (result is wrong from an xml/xslt point of view)
+    styledoc = LibXML::XML::Parser.string(style).parse
+    stylesheet = XSLT::Stylesheet.new(styledoc)
+    
+    out = stylesheet.apply( doc )
+    dump = stylesheet.dump_result( out )
+    assert_match( /<out>&lt;&gt;<\/out>/, dump)
+  end
 end
Index: ext/libxslt/libxslt.c
===================================================================
--- ext/libxslt/libxslt.c	(revision 131)
+++ ext/libxslt/libxslt.c	(working copy)
@@ -8,6 +8,7 @@
 VALUE cLibXSLT;
 VALUE cXSLT;
 VALUE eXSLTError;
+VALUE eXMLXSLTStylesheetRequireParsedDoc;
 
 /*
  * Document-class: LibXSLT::XSLT
@@ -58,6 +59,7 @@
   rb_define_const(cXSLT, "NAMESPACE_XALAN", rb_str_new2((const char*)XSLT_XALAN_NAMESPACE));
 
   eXSLTError = rb_define_class_under(cLibXSLT, "XSLTError", rb_eRuntimeError);
+  eXMLXSLTStylesheetRequireParsedDoc = rb_define_class_under(cLibXSLT, "ResultError", rb_eRuntimeError);
 
   ruby_init_xslt_stylesheet();
 
Index: ext/libxslt/ruby_xslt_stylesheet.c
===================================================================
--- ext/libxslt/ruby_xslt_stylesheet.c	(revision 131)
+++ ext/libxslt/ruby_xslt_stylesheet.c	(working copy)
@@ -4,6 +4,7 @@
 
 #include "libxslt.h"
 #include "ruby_xslt_stylesheet.h"
+#include "libxml/ruby_xml_document.h"
 
 /*
  * Document-class: LibXSLT::XSLT::Stylesheet
@@ -262,7 +263,96 @@
   return(INT2NUM(bytes));
 }*/
 
+/* call-seq: 
+ *   stylesheet.save_result(doc, to = $stdout) => number_of_bytes
+ * 
+ * Output an xml document, usually the result of an xslt transformation,
+ * to the specified output stream (an IO instance, defaults to $stdout).
+ * Output will be done according to the output specification in the xslt
+ * stylesheet.
+ * 
+ * Returns the number of bytes written.
+ */
+VALUE
+ruby_xslt_stylesheet_save_result(int argc, VALUE *argv, VALUE self) {
+  xmlDocPtr xdoc;
+  xsltStylesheetPtr xstylesheet;
+  VALUE document;
+  OpenFile *fptr;
+  VALUE io;
+  FILE *out;
+  int bytes;
+  
+  if (argc > 2 || argc < 1)
+    rb_raise(rb_eArgError, "wrong number of arguments (need 1 or 2)");
+    
+  document = argv[0];
+  
+  if (!rb_obj_is_kind_of(document, ruby_xslt_stylesheet_document_klass()))
+    rb_raise(rb_eTypeError, "Must pass in an XML::Document instance.");
 
+  switch (argc) {
+  case 1:
+    io = rb_stdout;
+    break;
+  case 2:
+    io = argv[1];
+    if (rb_obj_is_kind_of(io, rb_cIO) == Qfalse)
+      rb_raise(rb_eTypeError, "need an IO object");
+    break;
+  default:
+    rb_raise(rb_eArgError, "wrong number of arguments (1 or 2)");
+  }
+
+  GetOpenFile(io, fptr);
+  rb_io_check_writable(fptr);
+  out = GetWriteFile(fptr);
+
+  Data_Get_Struct(document, xmlDoc, xdoc);
+  Data_Get_Struct(self, xsltStylesheet, xstylesheet);
+  
+  bytes = xsltSaveResultToFile(out, xdoc, xstylesheet);
+  if ( bytes == -1 ) {
+    rb_raise(rb_eRuntimeError, "error saving document");
+  }
+
+  return(INT2NUM(bytes));
+}
+
+/* call-seq: 
+ *   stylesheet.dump_result(doc) => string
+ * 
+ * Dump an xml document, usually the result of an xslt transformation,
+ * and return the result as a string.
+ * Output will be done according to the output specification in the xslt
+ * stylesheet. Note that this includes the encoding of the string.
+ */
+VALUE
+ruby_xslt_stylesheet_dump_result(VALUE self, VALUE document) {
+// FIXME: set string encoding in ruby 1.9?
+  xmlDocPtr xdoc;
+  xsltStylesheetPtr xstylesheet;
+  xmlChar *result = NULL;
+  int len = 0, bytes = 0;
+  VALUE rresult;
+  
+  if (!rb_obj_is_kind_of(document, ruby_xslt_stylesheet_document_klass()))
+    rb_raise(rb_eTypeError, "Must pass in an XML::Document instance.");
+
+  Data_Get_Struct(document, xmlDoc, xdoc);
+  Data_Get_Struct(self, xsltStylesheet, xstylesheet);
+  
+  bytes = xsltSaveResultToString(&result, &len,
+				 xdoc, xstylesheet);
+  if ( bytes == -1 ) {
+    rb_raise(rb_eRuntimeError, "error dumping document");
+  }
+
+  rresult=rb_str_new((const char*)result,len);
+  xmlFree(result);
+  return rresult;
+}
+
 #ifdef RDOC_NEVER_DEFINED
   cLibXSLT = rb_define_module("LibXSLT");
   cXSLT = rb_define_module_under(cLibXSLT, "XSLT");
@@ -274,4 +364,6 @@
   rb_define_alloc_func(cXSLTStylesheet, ruby_xslt_stylesheet_alloc);
   rb_define_method(cXSLTStylesheet, "initialize", ruby_xslt_stylesheet_initialize, 1);
   rb_define_method(cXSLTStylesheet, "apply", ruby_xslt_stylesheet_apply, -1);
+  rb_define_method(cXSLTStylesheet, "save_result", ruby_xslt_stylesheet_save_result, -1);
+  rb_define_method(cXSLTStylesheet, "dump_result", ruby_xslt_stylesheet_dump_result, 1);
 }
Index: ext/libxslt/libxslt.h
===================================================================
--- ext/libxslt/libxslt.h	(revision 131)
+++ ext/libxslt/libxslt.h	(working copy)
@@ -27,5 +27,6 @@
 extern VALUE cLibXSLT;
 extern VALUE cXSLT;
 extern VALUE eXSLTError;
+extern VALUE eXMLXSLTStylesheetRequireParsedDoc;
 
 #endif
Index: lib/libxslt/stylesheet.rb
===================================================================
--- lib/libxslt/stylesheet.rb	(revision 0)
+++ lib/libxslt/stylesheet.rb	(revision 0)
@@ -0,0 +1,35 @@
+module LibXSLT
+  module XSLT
+    class Stylesheet
+      # options to be used for parsing stylesheets
+      PARSE_OPTIONS = LibXML::XML::Parser::Options::NOCDATA | LibXML::XML::Parser::Options::NOENT
+      class << self
+        # create a xslt stylesheet from a string
+        def string(xml)
+          doc = LibXML::XML::Parser.string(xml, :options => PARSE_OPTIONS).parse
+          return new(doc)
+        end
+        # create a xslt stylesheet from a file specified by its filename
+        def file(filename)
+          doc = LibXML::XML::Parser.file(filename, :options => PARSE_OPTIONS).parse
+          return new(doc)
+        end
+        # create a xslt stylesheet from an io object
+        def io(io_object)
+          doc = LibXML::XML::Parser.io(io_object, :options => PARSE_OPTIONS).parse
+          return new(doc)
+        end
+      end
+      # transform a xml to a string
+      def transform_to_string(doc)
+        rdoc = apply(doc)
+        return dump_result(rdoc)
+      end
+      # transform a xml to a file (specified by an output stream)
+      def transform_to_file(doc, io = STDOUT)
+        rdoc = apply(doc)
+        return save_result(rdoc, io)
+      end
+    end
+  end
+end

Property changes on: lib/libxslt/stylesheet.rb
___________________________________________________________________
Name: svn:executable
   + *

Index: lib/libxslt.rb
===================================================================
--- lib/libxslt.rb	(revision 131)
+++ lib/libxslt.rb	(working copy)
@@ -11,3 +11,4 @@
 require 'libxslt_ruby'
 
 require 'libxslt/deprecated'
+require 'libxslt/stylesheet'
