Archive

Posts Tagged ‘XML’

to_xml, almost deep enough

November 14th, 2007
Workout
   has_many :laps

Lap
   has_many :points
   belongs_to :workout

Point
   belongs_to :lap

I’d like to serialize it all. The first approach

render :x ml => workout.to_xml(:include => :laps)

Cool, we get workout data with laps, but no points.

Reading through the to_xml implementation it makes sense that you can only get to first level associations. For my purposes, I just wanted to avoid repeating my data model in my rxml files.

Here’s what I came up with.

module Builder
  class XmlMarkup
    def add_record!(record,options = {})
      options = {:builder => self,:skip_instruct => true}.merge!(options)
      ActiveRecord::XmlSerializer.new(record, options).serialize
    end

    def add_record_attributes!(record)
      record.attribute_names.each do|name|
        attr = ActiveRecord::XmlSerializer::Attribute.new(name,record)
        tag!( attr.name, attr.value.to_s, attr.decorations )
      end
    end
  end
end

Now, in my rxml view I can write this.

xml.instruct!( :x ml, :version=>"1.0", :encoding=>"UTF-8" )
xml.workout do
  xml.add_record_attributes!(@workout)
  xml.laps do
    @workout.laps.each do|lap|
      xml.add_record!(lap, :include => :points)
    end
  end
end

Software , ,

Using nsISAXXMLReader and my nsIChannel Woes

May 29th, 2006

My first pass was to use nsIIOService to create an nsIChannel. This worked and gave me time to focus on getting my parser methods correct.

function runRestoreFromChannel( urlSpec, progress )
{
 var ioService = CC["@mozilla.org/network/io-service;1"].getService(CI.nsIIOService);
 var reader = CC["@mozilla.org/saxparser/xmlreader;1"].createInstance(CI.nsISAXXMLReader);
 var channel = ioService.newChannel( urlSpec,null,null );

 channel.notificationCallbacks = progress;
 reader.contentHandler = new RestoreParser( broker, progress );

 reader.parseAsync( null );
 channel.contentType = "text/xml";
 //channel.loadFlags = CI.nsIRequest.LOAD_BACKGROUND;
 channel.asyncOpen( reader, null );
}

This solution has one major flaw. The nsIFileInputStream that’s created reads in very large chunks from the filesystem for performance. The problem is during my parse I do some fairly intensive I/O operations as I read my data in. But since, almost all the data is read in by the nsIInputStream, it means my GUI is locked up while I’m processing the data. To get around this I thought about writing my own nsIStreamListener and controlling how the data is passed to my nsISAXXMLReader. The problem there is I’d either have to buffer all the data or still be stuck in a loop on the main thread blocking the UI. Ideally, I’d be able to run the parser in a background thread at full speed. As a simple workaround I can use nsIInputStreamPump. It works almost identically to using the nsIChannel, except I can specify how much data to read at a time (i.e. the chunk size). There was one minor little problem that can be worked around using a custom nsIStreamListener around the nsISAXXMLReader.

Here’s the code for setting up the parser:

var reader = CC["@mozilla.org/saxparser/xmlreader;1"].createInstance(CI.nsISAXXMLReader);
var filestream = CC["@mozilla.org/network/file-input-stream;1"].createInstance(CI.nsIFileInputStream);
var pump = CC["@mozilla.org/network/input-stream-pump;1"].createInstance(CI.nsIInputStreamPump);
var ioService = CC["@mozilla.org/network/io-service;1"].getService(CI.nsIIOService);
var channel = ioService.newChannel( urlSpec,null,null );
var uri = ioService.newURI( urlSpec, null, null );

filestream.init(file, 0x01, 0444, 0);

reader.contentHandler = new RestoreParser( broker, progress );

reader.parseAsync( null );

pump.init( filestream, -1, -1, StreamChunkSize, 1, false );
reader.baseURI = uri;
pump.asyncRead( new StreamListener( reader, progress, channel ), null );

Here’s the code for wrapping the nsIStreamListener:

function StreamListener( reader, progress, channel )
{
 this.reader = reader;
 this.progress = progress;
 this.channel = channel;
}
StreamListener.prototype = {
 reader:null,
 onStartRequest: function( request,context ){
   dump( "starting\n" );
   // XXX: passing channel here to work around bug in nsParser.cpp
   this.reader.onStartRequest( this.channel, context );
 },
 onDataAvailable: function( request, context, input, offset, count ){
   dump( "offset: " + offset + ", count: " + count + "\n" );
   // XXX: passing channel here to work around bug in nsParser.cpp
   this.reader.onDataAvailable( this.channel, context, input, offset, count );
   this.progress.onProgress( request, context, offset, offset + count );
 },
 onStopRequest: function( request, context, status ){
   // XXX: passing channel here to work around bug in nsParser.cpp
   this.reader.onStopRequest( this.channel, context, status );
 }
};

Software , , ,