[Gregor.Media]

Gregor.Media

Sunday, November 5, 2006

Content Rendering

Note: This project is still under construction, altough the basic nuts and bolts are in place.

Gregor.Media has started out as a simple CGI application providing a configurable web interface to my personal music database. The original version can still be found at the downloads page.

The domain of an organizer application for music and other media is also the reason for the project name. Somehow, I got stuck with that name. So let's be historical, and interpret "media" to mean "content", and we'll be fine.

Various Output Formats

Gregor.Media is meant for rendering textual content of any kind. While so far, the focus is on HTML rendering, there's more on the horizon:

All of this has be integrated into WebEdit.NET. While WebEdit can already talk to a web server (using the built-in HTTP storage provider) as well as databases (with the DataDocs AddIn), what's missing is true interactivity. So I'm thinking along the lines of interfacing with a web server, but having smarter document views than are available right now. For example, Web View provided by the WebBrowser AddIn doesn't have a clue about hyperlinks; and nowhere are there real forms for editing structured data.

Last but not least, Gregor.Media should be suitable for code generation, altough it probably won't add much to Gregor.Core's text generation features.

Handling Output

The basic Gregor.Media project offers a synchronous request/response model for clients. It can be hosted by a CGI application, and also it offers a few utility modules for making just that easier - but that's just one option.

As a class library, it is easy to integrate into any application, like a Windows application that needs flexible UI/content rendering, or a simple console program (a code generator, for instance).

Project Interna

Mainly, Gregor.Media uses the following goodies of the Gregor.NET framework:

The solution actually consists of several projects, as outlined below.

Gregor.Media Project

The central project is Gregor.Media, a class library. It defines the base rendering mechanisms and configuration options (see the root namespace, and the Configuration sub namespace). Everything else (the types in all other namespaces) is optional:

The rendering engine is configured with several configuration files. There is one starter file (see the Gregor.Media.Music project for an example), which describes the page structure, assemblies to load, as well as any additional parameters. It's represented by the Configuration.CConfigInfo class:

<Config>

    <Assembly name="Gregor.Media.Music.dll" />

    <Param name="config-name"       value="Media" />
    <Param name="config-file"       value="Gregor.Media.Music.xml" />
    <Param name="connection-string" value="Provider=... />
    <Param name="css-stylesheet"    value="/styles/media.css" />
    <Param name="trace"             value="0" />
	
    <Page name="Page1">
        <Sheet name="Sheet1" pathinfo="Sheet1.PathInfo.xml" />
        <Sheet name="Sheet2" pathinfo="Sheet2.PathInfo.xml" />
    </Page>
	
</Config>

Note that while the Gregor.Media libary parses all of the elements in this example, it doesn't process all of this information. Assemblies will be loaded and the page/sheet structure will be set up, but the parameters are stored only, to be used by domain-specific extensions (see below).

Assembly loading serves the purpose of supplying the data from which the content is to be rendered. The common case is accessing a database, and while Gregor.Media provides helpers for that (see above), there are in fact no restrictions whatsoever on the data model - all that's needed is a graph of objects.

A page (Configuration.CPageInfo) is the basic navigation target. Pages consist of one or more sheets, but a sheet (Configuration/CSheetInfo) may appear in several pages.

Sheets are actually path info documents linking to templates (see here for some background information):

<Sheet outfilename="Gregor.Media.Music.Sheet1.html">

    <Root select="Context.Root"
          template="Sheet1.Header.Template.html">

        <Artist select="Root.AllArtists[*]" expression="Artist">
            
        </Artist>

    </Root>

</Sheet>

The path info document for a sheet is the first place where the code interpreter comes into place. The primary entry point into domain-specific data is - by convention - the Context object. Its class should extend Gregor.Core.Collections.CGraphContext (which helps interfacing with object graph, and keeping track of changes as the text generator walks through that graph).

The code interpreter must implement Gregor.Core.ICodeInterpreter2, as does Gregor.NetConsole.Engine.CCurlyInterpreter. It is shared among several objects - the request, the text generator, the object graph - and it is also invoked from the templates.

Here's a template:

<div>
    <p>
        Sheet1 Header (Database: $Root.Name%)
    </p>
    <p>
        Number of Artists: $Root.AllArtists.Count%
    </p>
</div>

Gregor.Media.Music Project

This is a domain-specific extension. Its task is to provide data from my personal music database in a way that is easily accessible from the path information documents and templates, which it also supplies.

For now, this class library defines an extension to the context and the database connection classes, so that the interpreted code can find its way into the music object model through a central root.

Gregor.Media.Music.CgiConsole Project

This is a simple console exe runnable as a CGI application, specifically working with the Gregor.Media.Music project.

It's possible to write a generic CGI application that hosts the general content generation engine (Gregor.Media) as well as any domain-specific extension (like Gregor.Media.Music). Such an application would require a bit of configuration, and I'll probable implement one some time.

However, wiring it all together in a domain-specific host app isn't too difficult, either:

CConfigInfo config = MediaCenter.CreateConfiguration("Gregor.Media.Music");
config.Load(@"Gregor.Media.Music.xml");

CRequest request = MediaCenter.CreateRequest(config);
request.Parameters.AddRange(ServerEnvironment.GetQueryParams());

string sConfigName = config.Parameters["config-name"] as string;
string sConnect = config.Parameters["connection-string"] as string;

using(CMusicContext context = MusicCenter.CreateContext(sConfigName, sConnect))
{
    request.Interpreter.SetObject(CObjectGraph.VARIABLE_CONTEXT, context);

    CResponse response = request.GetResponse();
    string sText = response.GetText();

    ServerOutput.WriteResponseHeader();
    ServerOutput.WriteLine(sText);
}

That is most of the executable's code - but yes, it's easy to identify the generic parts.

Gregor.Media.Music.WinFormsClient Project

This is the Windows Forms equivalent to CgiConsole program. So far, it's just a little demo of how Gregor.Media can render XAML-like markup, which can be interpreted so as to create a WinForms GUI. Here's the path info:

<Sheet outfilename="Gregor.Media.Music.WinSheet1.html"
       literalbefore="&lt;WinForms&gt;"
       literalafter="&lt;/WinForms&gt;">

    <Root select="Context.Root"
          literalbefore="  &lt;ListView&gt;"
          literalafter="  &lt;/ListView&gt;">

        <Artist select="Root.AllArtists[*]"
                literalbefore="    &lt;Item&gt;"
                literalafter="    &lt;/Item&gt;"
                expression="Artist.Name">
        </Artist>

    </Root>

</Sheet>

The code is quite simple, but perhaps this screen shot will convey the general idea.

Gregor.Media.CodeGenerator Project

This is a helper project, compiling into a console executable. It uses the meta classes defined in Gregor.Media.DataAccess, which offer schema access for OleDb-accessible databases (CDatabaseInfo, CTableInfo, CColumnInfo) for generating C# classes. These classes can then be used in a specific object model.

The code generator uses standard text generation techniques (building a graph of meta data objects controlled with a path info document, and text templates, and the code interpreter).

It can be invoked from the command line, supplying a configuration file (like the one fed to the rendering engine itself) for reading DB connect info, a path info document, and the names of one or more tables.