Data islands

Scripts often don't have the same level of data and logic separation as programs in "traditional" languages. And this is for a good reason: logic in scripts changes about as often as the data being processed (and if it's not true, "traditional" compiled programming language, with type validation etc, would be, arguably, a better choice).

So a scripting language needs a way to hardcode data into the script itself in text, XML, or CSV. Ideally, it should be as easy to use external files too. CSV support may be surprising if there is XML, but I often find CSV and text a lot easier to read and modify than XML because of reduced verbosity.

For example,

Product, 	Count, 	Price
Cheese,		2,	4.99
Meat,		4,	9.99
Cereal,		9,	2.49

is a lot more readable than

<data>
	<product>
		<name>Cheese</name>
		<count>4</count>
		<price>4.99</price>
	</product>
	<product>
		<name>Meat</name>
		<count>4</count>
		<price>9.99</price>
	</product>
	<product>
		<name>Cereal</name>
		<count>9</count>
		<price>2.49</price>
	</product>
</data>

Rowset

More often than not when writing scripts there is a need to deal with flat lists of objects, not object hierarchies. Could be lists of files, SQL tables and so on.

To represent this type of data, there is rowset action, which contains multiple rows in it. Representation may different, whatever is more convenient to the writer.

For example, XML using a special action to for every record, with attributes being values.

<rowset id="data">
	<row name="Cheese" count="2" price="4.99" />
	<row name="Meat" count="4" price="9.99" />
	<row name="Cereal" count="9" price="2.49" />
</rowset>
<print>${=$~data.ToTextTable()}</print>

or "normal" XML using elements

<rowset id="data">
	<row><name>Cheese</name><count>2</count><price>4.99</price></row>
	<row><name>Meat</name><count>4</count><price>9.99</price></row>
	<row><name>Cereal</name><count>9</count><price>2.49</price></row>
</rowset>
<print>${=$~data.ToTextTable()}</print>

or CSV using comma or other column separator

<rowset id="data" csvOptions="default header ignoreEmpty">
	name, 		count, 	price,

	Cheese,		2,		4.99
	Meat,		4,		9.99
	Cereal,		9,		2.49
</rowset>
<print>${=$~data.ToTextTable()}</print>

All these notations are equivalent. The final line prints the rowset nicely as a table:

+--------+-------+-------+
| name   | count | price |
+--------+-------+-------+
| Cheese | 2     | 4.99  |
| Meat   | 4     | 9.99  |
| Cereal | 9     | 2.49  |
+--------+-------+-------+

Rowset may also be converted to a .NET DataTable using .ToDataTable() method.

By default columns are treated as text, but type may be explicitly specified. Also one rowset may be derived from another rowset with filtering and sorting. For example, the below prints total cost of cereal ( 2.49 * 9 = 21.41 ). For comparison, cost of meat is calculated using C# snippet with LINQ (this one requires .NET 3.0).

<!-- Create columns with types explicitly specified -->
<rowset id="data">
        <col name="name" type="string" />
        <col name="count" type="int" />
        <col name="price" type="decimal" />
       Cheese,		2,		4.99
	Meat,		4,		9.99
	Cereal,		9,		2.49
</rowset>

<!-- Create a new rowset, with only one row for cereal  -->
<rowset id='data-cereal' rowsetId='data' >
	<where isTrue='${=$name=="Cereal"}' />
</rowset>

<!-- Calculate total of that row -->
<foreach rowsetId="data-cereal">
	<set total='${=	$price * $count }' />
</foreach>

<!-- Print total cost of cereal -->
<print>Total cost of cereal is ${total|0}</print>

<?_
	var meat=(from r in c.Find<XS.RowSet>("data",true)
		  where (string)r["name"]=="Meat"
		  select r.GetInt("count")*r.GetDecimal("price")).Single();
	c.WriteLine("Total cost of meat is "+meat);
?>

Xml document

There is also a basic support for XML data islands, to represent hierarchical structures.

With the following definition

<xmldoc id="x1">
	<html xmlns="">
		<head />
		<body>
			<p id="par1">
				<b>Hello</b>, world!
			</p>
			<p id="par2">
				<i>Quick brown</i>, fox!
			</p>
		</body>
	</html>
</xmldoc>

The code below prints inner XML of paragraph par1

<print tr="trim expand">${= $~x1['//p[@id="par1"]'].InnerXml }</print>

This prints the first bold text in par1:

<print>${= $~x1.XValue('//p[@id="par1"]/b[1]/text()') }</print>

The data can be also accessed from C# code, using the usual XML parsing tools from .NET 2.0:

<?_ 
	// Find XmlDoc named x1 (throw exception if not found)
	XS.XmlDoc x=c.Find<XS.XmlDoc>("x1",true); 

	x["/html/body/p[@id='par1']"].InnerXml="<b>Good bye!</b>";
	c.WriteLine(x.XmlDocument.OuterXml);
?>

Finally, if executed on machine with .NET3, LINQ for XML can be used:

<?_
	var x=XDocument.Parse( c.Find<XS.XmlDoc>("x1",true).OuterXml); 
	c.Dump(x.Root.Descendants("p").ToArray());
?>

Also, the data may be loaded from file instead of being specified directly:

<xmldoc id="z" from="data.xml" />

There are many other possibilities, including conversions between XmlDoc/RowSet/.NET DataTable/text table/CSV format/XML etc.