I'm making a jquery clone for C#. Right now I've got it set up so that every method is an extension method on IEnumerable<HtmlNode>
so it works well with existing projects that are already using HtmlAgilityPack
. I thought I could get away without preserving state... however, then I noticed jQuery has two methods .andSelf
and .end
which "pop" the most recently matched elements off an internal stack. I can mimic this functionality if I change my class so that it always operates on SharpQuery objects instead of enumerables, but there's still a problem.
With JavaScript, you're given the Html document automatically, but when working in C# you have to explicitly load it, and you could use more than one document if you wanted. It appears that when you call $('xxx')
you're essentially creating a new jQuery object and starting fresh with an empty stack. In C#, you wouldn't want to do that, because you don't want to reload/refetch the document from the web. So instead, you load it once either into a SharpQuery object, or into an list of HtmlNodes (you just need the DocumentNode to get started).
In the jQuery docs, they give this example
$('ul.first').find('.foo')
.css('background-color', 'red')
.end().find('.bar')
.css('background-color', 'green')
.end();
I don't have an initializer method because I can't overload the ()
operator, so you just start with sq.Find()
instead, which operates on the root of the document, essentially doing the same thing. But then people are going to try and write sq.Find()
on one line, and then sq.Find()
somewhere down the road, and (rightfully) expect it to operate on the root of the document again... but if I'm maintaining state, then you've just modified the context after the first call.
So... how should I design my API? Do I add another Init
method that all queries should begin with that resets the stack (but then how do I force them to start with that?), or add a Reset()
that they have to call at the end of their line? Do I overload the []
instead and tell them to start with that? Do I say "forget it, no one uses those state-preserved functions anyway?"
Basically, how would you like that jQuery example to be written in C#?
sq["ul.first"].Find(".foo") ...
Downfalls: Abuses the[]
property.sq.Init("ul.first").Find(".foo") ...
Downfalls: Nothing really forces the programmer to start with Init, unless I add some weird "initialized" mechanism; user might try starting with.Find
and not get the result he was expecting. Also,Init
andFind
are pretty much identical anyway, except the former resets the stack too.sq.Find("ul.first").Find(".foo") ... .ClearStack()
Downfalls: programmer may forget to clear the stack.Can't do it.
end()
not implemented.Use two different objects.
Perhaps useHtmlDocument
as the base that all queries should begin with, and then every method thereafter returns aSharpQuery
object that can be chained. That way theHtmlDocument
always maintains the initial state, but theSharpQuery
objects may have different states. This unfortunately means I have to implement a bunch of stuff twice (once for HtmlDocument, once for the SharpQuery object).new SharpQuery(sq).Find("ul.first").Find(".foo") ...
The constructor copies a reference to the document, but resets the stack.