How to Start a Software Company 2.0

by Richard Rodger

       
 
How to Beat Nasty Interview Programming Tasks

Shane Bell does a write-up of an interview he went through. Apparently the company just dumped a programming exercise on him and left him with a pencil and paper for an hour. Nasty!

While the basic idea of a "real" programming test at interview is great, asking someone to do it with a pencil is just plain daft! This is a perfect example of cargo-culting. They know they should get people to program in an interview, they know they should ask a "tough" question. But then they invalidate the whole thing by testing "pencil-based-programming-acuity"! Whatcha building guys? A Babbage engine? Um, you know, how difficult is it, if you are going to the trouble of all this testing, to set up a locked down machine with no internet access?

Anyway, Shane runs through the exercise and his solution. He does pretty well. He also asks if there's a better solution.

Yes, Virginia, there is a Santa Claus!

And he lives at MIT OpenCourseWare. Specifically, the AI search lectures. Fantastic stuff.

Looking at the problem they gave Shane, finding a path through maze from top-right to bottom-left, it looks like you could throw an A* search at it and do pretty well. Add some iterative-deepening if you're feeling fancy and want to handle big mazes. Basically, you try to predict the best direction by calculating your current straight-line distance from the goal square at the bottom right, and choosing the next square as the one that gets you closest. If you get stuck in a cul-de-sac, backtrack out of it (Shane does use backtracking).

So how do you beat these nasty interviews? Know your search algorithms! Most of these "puzzles" can be solved with some sort of search. I'll bet you anything the guys who set this question where either a.) clueless, so a good algorithm will really impress them, or b.) not clueless and actually looking for a proper algorithm like A*. Either way you win!

@ 09:55 AM GMT+00:00 [ comments [4] ]   email this   links to this
 
 
Level 3!

Well you might have thought that I had given up on the touch typing. I've been trying to learn to touch-type for the last two years. It's all going tragically slowly. But, I can tell you that I am in fact touch-typing this very blog post — my first touch-typed blog post ever!

I'm not really there yet — my touch-typing is still slower than my "natural" typing. But I have, finally, cracked the notorious level three on the learn2type.com site. If you've read my previous post about learn2type, you'll remember that level three is this dreadfully unbalanced drill that goes straight into all the punctuation straight from the home row keys. It's a real killer for your enthusiasm. You have to be pretty dedicated to beat it. It took me over a year. While the learn2type site is pretty OK so far as learning to type goes, it does have some serious flaws. And I have not seen any updates in over a year. Still, I would recommend it overall &mdash the performance graphs in particular are very cool, providing good feedback on your progress.

My meta-strategy for learning to touch-type remains the same as before — try all the online tutoring sites one at a time until I have good speed and accuracy. Stay tuned…

@ 10:10 AM GMT+00:00 [ comments [0] ]   email this   links to this
 
 
Boxes and Lines, Boxes and Lines...

Charles Miller posted a great comment on his blog that absolutely cracks me up:

Pretty much any computing problem, given a sufficient level of abstraction, can be reduced to a diagram of boxes joined together with lines. At this level your solution will look startlingly simple, and you'll be able to sell it to someone.

So true, so true.

@ 11:03 AM GMT+00:00 [ comments [1] ]   email this   links to this
 
 
How to Create a Comment Archive Using CSV to Generate HTML

I've been trying to find a workable way to manage my comments for quite some time. By which I mean, the comments that I make on other people's blogs. You need to be able to go back and see if the conversation has progressed. It's also nice just to have a record of what you said and when you said it.

I was using CoComment for a while. This is a service that tracks comments on blogs. It's pretty cool. Trouble is, it only works for the main blogging engines, and you have to install a plugin. I removed all plugins from my Firefox recently because it was acting up, and I'm not keen on reinstalling just at the moment. In any case, the CoComment plugin tended to slow down non-blog sites (looking for comment forms I suppose).

So I've decided on a simpler solution: just have a page on my blog where all my comments are listed in reverse chronological order, with a link back to the relevant blog entry. I can skim through the first few to see if recent conversations have anything new. As for the old conversations, well, I guess I won't know if there are more comments. But that's "good enough" for the time being. The easiest way to build this page is cut-and-paste. Come up with a bit of HTML and copy it for each new entry. Yeah, it has to be done by hand, but hey! The archive of comments is interesting enough to be worth recording.

Here's the comments archive, so you can see what I mean.

Well, you're right, cut-and-paste is such a bad smell. It's better to have your data in a manageable format. So Ricebridge to the rescue! You can put the data into a CSV file and generate the HTML (or rather XHTML) from it. For example, here's a record of some comments:

Date,Blog,Link,Comment
2007-04-10,mariosalexandrou.com, \
  http://www.mariosalexandrou.com/blog/?p=291&c=y, \
  "Hey! I did all that already! Where's my six figures? love it :) "
2007-04-04,Tyner Blain, \
  http://tynerblain.com/blog/2007/04/03/ba-profit-center, \
  "It’s amazing how naming something almost completely defines it."
...

It's just a CSV file. Easy to update by hand. Whenever you make a new comment, throw in the details (date, blog title, link and comment text) at the top of the CSV file.

So then how do we turn this into HTML? Well, here's the HTML I'm producing from this CSV file:

<div class="commentbox">
  <div class="comment">
    <b>
      <span>2007-04-10</span>
      <a href="http://www.mariosalexandrou.com/blog/?p=291&c=y">mariosalexandrou.com</a>
    </b>
    <p>Hey! I did all that already! Where's my six figures? it :) </p>
  </div>
  <div class="comment">
    <b>
      <span>2007-04-04</span>
      <a href="http://tynerblain.com/blog/2007/04/03/ba-profit-center">Tyner Blain</a>
    </b>
    <p>It's amazing how naming something almost completely defines it.</p>
  </div>
</div>

It's a nice little microformat of sorts, I suppose.

To produce this, you need to take the CSV columns and place them into the right positions in the XML format. We're generating XHTML, which is just XML, which is just well-behaved HTML, so this is all cool and froody.

Using XML Manager, you can define a set of XPath expressions to handle this. And here they are:

each row     -> /div/div
'commentbox' -> /div/@class
'comment'    -> @class
Date         -> b/span
Blog         -> b/a
Link         -> b/a/@href
Comment      -> p

This creates a main <div class="commentbox"> containing a set of <div class="comment"> elements, one for each comment. The CSV columns all go into subelements of the comment div.

And here's the code to tie it all together:

CsvManager csvman = new CsvManager();
csvman.getCsvSpec().setStartLine(2);
csvman.getCsvSpec().setIgnoreEmptyLines(true);
List in = csvman.load("data/comment.csv");

RecordSpec rs = new RecordSpec("/html/body/div/div", 
    new String[] { "/html/body/div/@class", "@class", 
      "b/span","b/a","b/a/@href","p"});

List out = new ArrayList();
for( Iterator cI = in.iterator(); cI.hasNext(); ) {
  String[] inrow = (String[]) cI.next();
  String[] outrow = new String[] {"commentbox","comment",
    inrow[0],inrow[1],inrow[2],inrow[3]};
  out.add(outrow);
}

XmlManager xmlman = new XmlManager(rs);
xmlman.save("data/comment.htm",out);

You just load up the CSV, and spit it out again as XML... "there's nothing to it, really..."

And then all you do is dynamically include this file on your web page, and you're done!

tag gen:Technorati Tags: Del.icio.us Tags:
@ 02:27 PM GMT+00:00 [ comments [1] ]   email this   links to this
 
 
Ruby: A Quick Cleaner for Your Adwords Invoices

If you advertise on Google then you probably have to save and print your monthly adwords invoices. Especially if you want to get the VAT back! In fact, I do this with pretty much all my online services. I need to have copies of invoices for my own records and for end-of-year accounts. It's a pain in the ass, frankly. You have to go to each site, call up the invoice page, launch the printable version (if they have one), save the page and print it. Don't know about you, but what a waste of precious coding time!

Somebody should write a web service to consolidate online service payments. Yeah, sure, the offline payments world has been done. You can go to sites like BillPay.ie and sort it all out. Where's the same solution for my online accounts? Isn't it ironic that the most connected, most virtual services, are the ones I'm putting the most physical labour into?

And what really bugs me is when I save a HTML page, FireFox saves all the ancilliary files in a [name]_files folder. Normally this is what you want I suppose, but for a series of monthly invoices it's not the right thing at all. You end up with loads of folders all containing the same set of CSS, JavaScript, and image files (by the way, can we all agree to call them folders, and not directories? Saves on the typing, you know...). Which is annoying, because you do actually need all those extra files to make the invoices look pretty if you ever want to print them out again. So really what you want is for all the extra files to live in one folder, say, files, and for all the downloaded HTML pages to refer to this folder.

And the other reason for having them all in the same folder is to support version control. Call me paranoid, but I put everything into Subversion. So handy. And multiple files that are all really the same is a completely pointless state of affairs when you add Subversion into the mix. Oy vey!

Up to now I've half-solved this problem whenever it bugged me enough by recording a quick Emacs macro and flying through the files. But you can only record the same macro so many times in your life without cracking up. Time for automation: "Why program by hand in five days what you can spend five years of your life automating?"

Well, let's do it in Ruby and then we can all go home after lunch. Here, for your use, should you have this exact same problem, is a little Ruby script to fix the links in the downloaded HTML pages, and copy the ancilliary files into a files folder (folder, yeah?). Notice that I don't delete the old files and folders. Trust me, never delete files in a hacked-together five-minute script, you will very much regret it. Delete stuff by hand.

require 'fileutils'

date_file_paths = Dir.entries('.').select { |f| 
  f =~ /\d\d\d\d\d\d\d\d\.htm/ }
date_names = date_file_paths.map { |f| 
  (f.match /(\d\d\d\d\d\d\d\d)/)[1] }

max = 0
date_names.each { |n| 
  content = []
  File.foreach("#{n}.htm","r") { |line|
    content << line.gsub(/#{n}_files/,'files')
  }
  File.open("#{n}.htm","w") { |f|
    f.print content
  }
  max = n.to_i if n.to_i > max
}

FileUtils.cp( Dir.entries("#{max}_files").select{ |n| 
  n != '.' && n != '..' }.map { |n| 
    "#{max}_files/#{n}" }, 'files' )

Oh yeah, one more thing, I save the files using the naming convention YYYYMMDD.htm (you might have guessed, I suppose).

tag gen:Technorati Tags: Del.icio.us Tags:
@ 08:02 PM GMT+00:00 [ comments [0] ]   email this   links to this
 
 
Beat ya to it Joel!

Ah, imitation, the sincerest form of flattery :)

Now Joel, just because I posted my demand curve doesn't mean you have to post yours.

Ah, only kidding.

Actually it's good to see some data from a more established business. I definitely made some pricing mistakes at the start. This type of data should give anyone starting a micro-ISV the confidence to go for higher margins.

Don't be scared. People will pay.

@ 11:04 AM GMT+00:00 [ comments [0] ]   email this   links to this
 
 
Squidoo?

I thought I would give Seth's Squidoo thing a go.

Of course, what other subject to create content on, but CSV files! :)

Let's see if it generates any traffic…

@ 04:29 PM GMT+00:00 [ comments [0] ]   email this   links to this
 
 
New Title 2.0

I've decided to change the title of my blog. I haven't been happy with the old one for a while.

It was a play on the film How to Succeed in Business Without Really Trying, but I don't think it ever really worked.

I've been working on some new directions for the business, with new products due for release later this year, so the 2.0 moniker seems right. Now I just need to check with Tim...

@ 11:45 AM GMT+00:00 [ comments [0] ]   email this   links to this
 
 
Update: Firefox crashing with too many fonts

Yep, you read right. Too many fonts and bye-bye Firefox.

And by "too-many" I mean one more than the number of standard windows fonts…

You know, it really sucks. Firefox is so cool, but this is just not on. I was using Opera before I found this fix and you know what? Opera kicks ass. It's a damn fine browser and it hardly ever crashes these days. Back in the day I never used Opera because it was always bloody crashing. Must be Firefox's turn eh?

This "Interwebnet" thing will never catch on…

@ 10:29 AM GMT+00:00 [ comments [0] ]   email this   links to this
 
 
Firefox and Thunderbird Crashing Randomly

I am not a happy camper.

Firefox and Thunderbird on my Windows 2000 box are crashing completely at random. It seems like some sites and emails are toxic! Thunderbird even dies sometimes when scrolling.

Being of the software mindset I have poked and prodded at this problem a bit, but I'm stuck. Here's the killer: both work perfectly on my XP laptop. Killer huh?

And yes, I've done all usual stuff like reinstalling, new profiles, compacting, yada yada, etc. etc.

Like I said, not a happy camper at all, at all.

@ 09:59 AM GMT+00:00 [ comments [2] ]   email this   links to this
 
 
Race Condition in Ruby GServer

I've been playing around with Ruby lately, trying to write a UDP server. Of course, the way to write servers in Ruby is apparently (I'm still a bit new at this Ruby malarky) to subclass GServer.

And what a fine piece of code GServer is! Really does the job and shows just how easy it is to do stuff in Ruby. If you want to a TCP server.

But I needed a UDP server. So after much messing about I came up with the following half-baked solution. Please feel free to tear to pieces. I should remind you however that this code is WOMM certified.

# with apologies to John W. Small

require "socket"
require "thread"

class UServer

  DEFAULT_HOST = "127.0.0.1"
  DEFAULT_TRANSPORT = "TCP";

  def serve(io,content)
  end

  @@services = {}   # Hash of opened ports, i.e. services
  @@servicesMutex = Mutex.new

  def UServer.stop(port, 
                   host = DEFAULT_HOST, 
                   trans = DEFAULT_TRANSPORT)
    @@servicesMutex.synchronize {
      @@services[host][port][trans].stop
    }
  end

  def UServer.in_service?(port, 
                          host = DEFAULT_HOST, 
                          trans = DEFAULT_TRANSPORT)
    @@services.has_key?(host) and 
      @@services[host].has_key?(port) and
      @@services[host][port].has_key?(trans)
  end

  def stop
    @connectionsMutex.synchronize  {
      if @serverThread
        @serverThread.raise "stop"
      end
    }
  end

  def stopped?
    nil == @serverThread
  end

  def shutdown
    @shutdown = true
  end

  def connections
    @connections.size
  end

  def join
    @serverThread.join if @serverThread
  end

  attr_reader :port, :host, :trans, :maxConnections
  attr_accessor :stdlog, :audit, :debug

  def connecting(client)
    addr = client.peeraddr
    log("#{self.class.to_s} #{@host}:#{@port} #{@trans} client:#{addr[1]} " +
        "#{addr[2]}<#{addr[3]}> connect")
    true
  end

  def disconnecting(clientPort)
    log("#{self.class.to_s} #{@host}:#{@port} #{@trans}" +
      "client:#{clientPort} disconnect")
  end

  protected :connecting, :disconnecting

  def starting()
    log("#{self.class.to_s} #{@host}:#{@port} #{@trans} start")
  end

  def stopping()
    log("#{self.class.to_s} #{@host}:#{@port} #{@trans} stop")
  end

  protected :starting, :stopping


  def error(detail)
    log(detail.backtrace.join("\n"))
  end

  def log(msg)
    if @stdlog
      @stdlog.puts("[#{Time.new.ctime}] %s" % msg)
      @stdlog.flush
    end
  end

  protected :error, :log

  def initialize(port, host = DEFAULT_HOST, trans = DEFAULT_TRANSPORT, maxConnections = 4,
    stdlog = $stderr, audit = false, debug = false)
    @serverThread = nil
    @port = port
    @host = host
    @trans = trans
    @maxConnections = maxConnections
    @connections = []
    @connectionsMutex = Mutex.new
    @connectionsCV = ConditionVariable.new
    @stdlog = stdlog
    @audit = audit
    @debug = debug
  end


  def start(maxConnections = -1)
    raise "running" if !stopped?
    @shutdown = false
    @maxConnections = maxConnections if maxConnections > 0
    @@servicesMutex.synchronize  {
      if UServer.in_service?(@port,@host,@trans)
        raise "Port already in use: #{host}:#{@port} #{@trans}!"
      end

      if "TCP" == @trans
        @server = ContentTCPServer.new(@host,@port)
      else
        @server = ContentUDPServer.new(@host,@port)
      end

      @@services[@host] = {} unless @@services.has_key?(@host)
      @@services[@host][@port] = {} unless @@services[@host].has_key?(@port)
      @@services[@host][@port][@trans] = self;
    }
    @serverThread = Thread.new {
      begin
        starting if @audit
        while !@shutdown
          @connectionsMutex.synchronize  {
          puts "start @con.size=#{@connections.size}"
             while @connections.size >= @maxConnections
               @connectionsCV.wait(@connectionsMutex)
             end
          }

          client, port, close, content = @server.accept

          Thread.new(client,port,close,content)  { 
            |myClient, myPort, myClose, myContent|

            @connectionsMutex.synchronize {
              @connections << Thread.current
            }
            begin
              serve(myClient,myContent) if !@audit or connecting(myClient)
              puts "finished serve"
            rescue => detail
              error(detail) if @debug
            ensure
              begin
                if myClose
                  myClient.close
              end
              rescue
              end

              @connectionsMutex.synchronize {
                @connections.delete(Thread.current)
                @connectionsCV.signal
              }

              disconnecting(myPort) if @audit
            end
          }
        end
      rescue => detail
        error(detail) if @debug
      ensure
        begin
          @server.close
        rescue
        end
        if @shutdown
          @connectionsMutex.synchronize  {
             while @connections.size > 0
               @connectionsCV.wait(@connectionsMutex)
             end
          }
        else
          @connections.each { |c| c.raise "stop" }
        end
        @serverThread = nil
        @@servicesMutex.synchronize  {
          @@services[@host][@port].delete(@trans)
        }
        stopping if @audit
      end
    }
    self
  end

end


class ContentTCPServer

  def initialize( host, port )
    @server = TCPServer.new(host,port)
  end
  
  def accept
    client = @server.accept
    return [client,client.peeraddr[1],true,client.gets(nil)]
  end  

  def close 
    @server.close
  end

end


class ContentUDPServer

  def initialize( host, port )
    puts "init"

    @socket = UDPSocket.new
    puts "new s: #{@socket} on #{host}:#{port}"

    @socket.bind(host, port)
    puts "bound: #{@socket}"
  end
  
  def accept
    puts "accept"

    packet = @socket.recvfrom(1024)
    return [@socket, 0, false, packet[0]]
  end  

  def close 
    @socket.close
  end

end

Lovely! Who says Java can't be translated into Ruby? I even used duck typing!

Anyway, see if you can spot the significant difference in thread handling between this and the original GServer.

Well, OK, here it is:

GServer says:

@connections << Thread.new(client)  { |myClient|
  ...
}

I say:

Thread.new(client,port,close,content)  { |myClient, myPort, myClose, myContent|
  @connectionsMutex.synchronize {
    @connections << Thread.current
  }
  ...
}

What happened was that UDP packets were handled so quickly that the thread was (mostly) never placed in the @connections list until after it was (supposedly) removed from the list by

@connectionsMutex.synchronize {
  @connections.delete(Thread.current)
  @connectionsCV.signal
}

at the end of the request thread. Seems like a nasty race condition to me. My code just makes sure that the thread is placed into the @connections list before nasty stuff can happen.

Am I right? You tell me...

@ 03:34 PM GMT+00:00 [ comments [2] ]   email this   links to this
 
 
Demand Curve for Java Software Components

I'm going to give some insider information. When I was starting up my components business I found it nearly impossible to get an data on what my sales would look like. So now that I've had a couple of years to collect some data on my own products, I'm going to let you have a little look at it.

The most critical decision you face for a new product is how to price it. There's all sorts of stuff written about this. And all sorts of names for the various strategies: skimming, penetration, cost-plus, etc. Go read Joel for a nice introduction.

Anyway, what I'm going to give you is my demand curve, and my revenue curve. Hmm. I don't know if it's called a revenue curve, but it seems like a good name. It's the one where instead of showing price, like in the demand curve, you show total revenue. Basically, price by units sold. Both curves show values per month. Right, on with the show:

My Demand Curve

My Revenue Curve

Ah. Number's eh? Actual revenue numbers? ROFL mate!

Nope, sorry, can't give you actual revenue numbers or units sold. Confidential business information and all that.

Still, if you're thinking of getting into the software components business, these curves will give you something to chew on. Let me explain them a bit more.

First, the demand curve does show the product price. That's public knowledge. I have collected my data from three pricing periods. I started with $97 and stayed with that price for the first 16 months. I then moved to $47 for a further 10 months. And I've been on $170 for the last two months. This gives a nice bit of data to work with. We can plot three points on the demand and revenue curves and interpolate between them to get some idea of the shape of the curves.

Second, the product I'm focusing on is actually the single developer versions of CSV Manager and XML Manager. The single developer version is the entry level version of these components and the biggest seller. Both products are also sufficiently similar in style and function that we can acceptably merge their data sets. These curves are representative of the majority of Ricebridge sales.

Third, the scales are linear and they are not zero-based. The curves give you information about the relative values between the three prices.

So what are the curves telling you? Well first, software components ain't no Giffen good. Bummer dude. The demand curve is pretty normal. Push up the price and no-one buys. Flog 'em cheap and you'll get a load of buyers.

More interesting is the revenue curve. This is what actually helps you decide what price is going to make the most money. For software you can pretty much ignore unit costs. The main cost to you of selling an extra unit is just your payment processing fees and they increase linearly. Looking at my curve you can see that there's a sweet spot between the $97 and $170 prices.

So should I reprice at $120? Would you?

Well I'm not going to. There's not enough data for the $170 price yet. I have a feeling it will hit sales harder than it has to date. I think I've just been lucky so far. That pushes the maximum point of the curve closer to $97. Remember, don't panic. Get the data.

You can tell that the $47 price point was a disaster. I stuck with it for too long. The data does not lie. It may have generated higher sales volumes, but overall revenue was significantly down. But hey, I had to know.

Of course, the problem with this entire analysis is that software products change. New versions get released. CSV Manager 1.2 came out last November.  It has new features so you get more for a given price. Let's just ignore that inconvenient factoid — it sorta messes up my lovely graphs!

To return to my pricing decision, I think the only way is up, actually. Given that the revenue difference between $170 and $97 is not that big, and given that new versions will be better value for money, I think on the whole that my price is pretty much about right for the moment. Yes, I am sacrificing volume. It's probably a skimming strategy. Then again, a price penetration strategy against open source (most of the alternative components) would be pretty nuts.

So there you go, if you're thinking of entering the software components market, now you have a bit more to go on. If you're already in the market, you might want to post something similar…

@ 05:50 PM GMT+00:00 [ comments [7] ]   email this   links to this
 
 
StelsXML XML JDBC Driver Launched!

I'd like to announce the release of a really cool new product by J-Stels Softare: StelsXML.

"StelsXML is a JDBC type 4 driver that allows you to perform SQL queries and other JDBC operations on XML files. With the StelsXML JDBC driver, you can easily access data contained in your XML documents by using standard SQL syntax and XPath expressions. The driver is completely platform-independent."

StelsXML uses Ricebridge XML Manager as its underlying XML engine. As you can imagine we're pretty happy to have been chosen for the job and we can heartily recommend the final product. Yet another way to break the impedance mismatch between Java and XML.

So go check StelsXML out!

@ 09:13 AM GMT+00:00 [ comments [0] ]   email this   links to this
 
 
CSV Manager 1.2.1 Released!

This is a new release of Ricebridge CSV Manager. New features include support for Java Beans, a pull/push streaming API for loading and saving CSV, and a simplified set of load and save methods. The example code has been expanded and now includes line-by-line explanations. It's all detailed on the What's New page

We're also introducing a new pricing scheme. A free single developer XML Manager license (worth $170) is included FREE with every CSV Manager license. PLUS, we include a free SIX MONTH email support package (worth $1500) with each purchase. And if you're an independent contractor, we've introduced a new option just for you: claim a 50% discount when you link to us from your site or blog!

This version is fully backwards compatible with CSV Manager 1.1 so you won't need to change your existing code.

Existing Ricebridge customers are invited to download the free upgrade from their user accounts.

@ 11:19 AM GMT+00:00 [ comments [0] ]   email this   links to this
 
 
Pricing Changes for the Next Release of CSV Manager

We're just putting the finishing touches on the next release of CSV Manager.

It's been a bit of a long haul this one. I decided to alter the naming convention for our CSV loading and saving methods, so that they would be easier to learn. Instead of having loads of methods for each type of data source (File, InputStream, etc), I refactored the API so that the load and save methods take an Object as the data source, and work out for themselves what to do with it. Much easier! Except that it meant rewriting lots of documentation…

I've also decided to add a six-month email support package into every purchase. We were supporting all customers anyway, so let's make it official. This package is worth $1500 by the way. Nice!

And because CSV Manager and XML Manager are really designed to work together, you now get a FREE single-developer license of XML Manager with every CSV Manager purchase. If you'd like to know more about how these two products can be integrated, read our XML to CSV and back again article.

But we are going to put our prices up. There's no way round that one. The price change mostly covers the six-month email support package. We're not a fire-and-forget company. If you use our stuff, we do want to help you get it working for your project. So this seems a better match with what people need. But yes, prices have gone up. Sorry guys! :)

There is some good news. If you're an independent contractor (and we'll have a pretty loose definition of this), and you mention us on your blog, then you can claim a 50% discount on ALL our products. And if you don't have a blog, we'll work something out. This will be a trust thing, so don't be shy – just ask!

And if you've been surfing round our site in the last two months and you think you should get the old prices, well, we agree. Just send us a mail explaining the situation and we'll sort you out. This should help ease the pain a bit. Only valid for 2006, etc., etc.

So what are the new prices? We'll be launching very soon, so you'll find out this week… stay tuned!

@ 12:02 PM GMT+00:00 [ comments [0] ]   email this   links to this
 
 
 
YahooBloglines
NewsgatorMSN
Google Readerdel.icio.us FurlSubscribe with myFeedster
« June 2007 »
SunMonTueWedThuFriSat
     
1
2
3
4
5
6
7
8
9
10
11
12
15
16
17
18
19
20
22
23
24
25
26
27
28
29
30
       
Today

All | General | Java | Business | Fun | Perl | Rant | Ireland | Web
[This is a Roller site]
[Valid Atom 1.0] [Valid RSS]
Technology Blog Top Sites
Blogarama - The Blogs Directory

Blog Directory & Search engine

Blog Flux Directory
Irish Blogs
 View My Public Stats on MyBlogLog.com

Performancing
Enter your Email


Powered by FeedBlitz
Theme adapted from Sotto.
 
Ricebridge XML Manager
  • Convert XML to a table of data
  • Convert XML to CSV, and CSV to XML
  • High-speed, single-pass XPath
  • Memory-stable and fault-tolerant
  • Loads of documentation
  • Cut-and-paste code examples
  • Find a bug, get a gift cert
Ricebridge Java XML Manager Component


Ricebridge CSV Manager
  • Convert CSV to a table of data
  • Handle any type of delimited file
  • Memory-stable and fault-tolerant
  • Loads of documentation
  • Cut-and-paste code examples
  • Find a bug, get a gift cert
Ricebridge Java CSV Manager Component


Popular Posts

 Sign up for MyBlogLog.com
Alertra Website Monitoring Service
Get Chitika eMiniMalls
Solo Tees
BlogJet