Thx, but I can write bespoke solutions quite happily. I was rather hoping for a "catch all" solution.
I have instead opted to create a pipelined table function, which accepts a weak refcursor, and you can use a select statement to generate a delimited line of text, which will then be parsed and converted into rows by the pipelined function.
select * from table(pkg_tools.pipe2rows(cursor(select a || '|' || b from tab1)));
select * from table(pkg_tools.pipe2rows('a|bb|ccc'));