Rendering to Objects+solution
I have a branch to investigate the concept. This is an opportunity to refactory WikiContent which is designed to render and Strings, with ObjectContent that represents the content as parsed into "Chunks", then there is flexibility to serialize directly, or to defer it to the render stage.
+Chunks
Wagn processes content for inclusions, links, URLs and any other content that needs to be processed during rendering. Below is an image showing the classes involved in the current implementation. The renderers use WikiContent which inherits from String, so the render code creates a WikiContent from the raw content in the card database, and uses that object to create a rendered string for the card to mix with the other content of the cards context.
+WikiContent Diagram
All of this would be pretty straigthforward except for one special case, the transcluded chunks. This is also where some of Wagn's power is implemented, the feature of including the contents of another card when rendering a base card, and then applying this recursively. The inclusion is contextual, so the renderer needs to supply a block to render!, but only the case of this inclusion feature in Chunk::Tranclude actually yeilds to the block. This occurs in the chunk method unmask_text.
Because the final result will be expanded into a single string, the ChunkManager masks the chunks internally when in parses out the chunks on initialization. It goes through each type of chunk, and turns the source syntax into chunk:id:classchunk where :id is a number assigned to the chunk and :class is the string name of the type.
The method render! turns these masked chunks into the rendered forms. Originally Wagn had only one renderer, so the unmasking would be one form for links, but with new renderers, the link unmasking calls methods that the different renderers can override
Enter Object Rendering
This all works very well until you try to render to json. The Rails level render really prefers that you give it a ruby object, that it will process like this: @object.as_json.to_json
When you render as: render :json=>@object, :format=> ...
The method as_json has a default implementation supplied by Rails, which is nice because it will give you something, but it will probably give you way more than you wanted, so you implement your own as_json for any classes you need to render. So, lets move to the next diagram with a new way of producing content as Objects instead of Strings. It will share the chunk types from the above WikiContent classes. Some of the internal APIs will change a little, but all the regular expressions are shared so we are implementing the same syntax.
Since we are working with objects, we can retain the original nesting and structure until final serialization in to_json, and we don't have to mask the chunks, we just turn them into Chunk objects. The investigative branch linked above (gerryg/wagn - obj_render) adds a second kinds of Content that is used by renderer as an alternative to the original WikiContent class which is still used for the Html and base renderer classes. This make this implementation more stable, but in principle we can and probably will refactor the WikiContent usage out of the code. There are some additional parts to doing that as the tracking of wiki refererences is supported in part by WikiContent. We want to make a clearer seperation of the reference tracking :Refactor Reference Tracking.
We add a new class method t the ChunkManager, split_content. Instead of calling init_chunkmanager after initializing the WikiContent object, ObjectContent#new uses this method to iterate through the chunks and strings in the content producing an Array of String and Chunk typed objects (these objects will be one of the ACTIVE_CHUNKS classes), and stores this array as the value of the new ObjectContent (@obj attribute). The ObjectContent#as_json method just returns @obj.as_json, which will end up calling .as_json on each Chunk and String.
Currently on the branch, Chunk::Abstract#as_json is like this:
def as_json(options={})
@unmask_text || @unmask_render|| "not rendered #{card and card.name}"
end
Most of the chunk types set @unmask_text at initialization, so they will render as that string always, the yield in Chunk::Transclude#unmask_text return value is saved to @unmask_render, and @unmask_text will be unset for this case, so we will get what it was in the last ObjectContent#render! Probably we can refactor away from using render!, but then we would have to supply a block to Chunk::Transclude#as_json because the transcusions would need to be expanded at this point. The "not rendered..." is debugging, one of them should be set when we call this.