4

I have been trying the fantabulous CsQuery library, which is basically a .NET port for jQuery allowing the use os CSS selectors and most of jQuery's functionalities.

I am using it to parse and edit a batch of HTML files (particularly, editing some attributes of different DOM elements).

The following C# snippet shows sort of what I'm doing, with the JavaScript/jQuery equivalent code in the comments.

FileStream doc = File.Open(/*some html file*/);    

CQ dom = CQ.Create(doc);        // $dom = $(document);
CQ images = dom.Select("img");  // $images = $('img');
images.Attr("src","#");         // $images.attr('src','#');

dom.Save(/*Output path*/); // No jQuery equivalent, this just saves the file.

This works perfectly: if I check the output file, all the image's src values are now #.

Anyway, if I use the Each block (which seems to work nicely using C# lambda expressions to simulate javascript's function passing) the changes won't apply to the output file:

FileStream doc = File.Open(/*same html file*/);

CQ dom = CQ.Create(doc);                  // $dom = $(document);
CQ images = dom.Select("img");            // $images = $('img');
images.Each( (element) => {               // $images.each( function(){
  CQ cqElement = CQ.Create(element);      //   $element = $(this);
  cqElement.Attr("src","#");              //   $element.attr('src','#');
  Messagebox.Show(cqElement.Attr("src")); //   alert($element.attr('src'));
});                                       // });

dom.Save(/*Output path*/); // No jQuery equivalent, this just saves the file.

Despite the Messabox showing "#" for every image in my DOM (which means that the cqElement gets the change), the output file doesn't get the changes.

I think the key line causing the problem is CQ cqElement = CQ.Create(element); since it creates a brand new CsQuery object. In fact, if just after the first Messagebox I pop up another one like the following one, it will not have the changes made to the cqElement

Messagebox.Show(dom.Html()); // alert($dom.html());

Any idea on how could I solve this issue?

Benjamin Gruenbaum
  • 270,886
  • 87
  • 504
  • 504
Sam
  • 1,222
  • 1
  • 14
  • 45

1 Answers1

4

You're absolutely right: CQ cqElement = CQ.Create(element) is the problem. This is at the core of the most siginficant difference between CsQuery and jQuery. With jQuery, there's only one DOM. With CsQuery, there's no browser, so there can be any number of distinct DOMs. Each time you use Create it's a brand new DOM; whereas jQuery methods/selectors return a new CQ object bound to the same DOM.

The correct way to "wrap" a DOM element as a CQ object like $(element) is with new and not Create, e.g.

CQ cqElement = new CQ(element);

There's also a shortcut that's a method on IDomObject:

CQ cqElement = element.Cq();

These keep the element in the same DOM. The Create methods always produce a new DOM, and when created from elements, actually clones them so the original DOM is not affected. (Elements can only belong to a single DOM; if you were to create two different DOMs and add elements from one to another using a method like Append, they would be removed from the source. Create automatically clones them.).

I am sure this could be made more clear in the documentation :)

Jamie Treworgy
  • 23,934
  • 8
  • 76
  • 119
  • This is ultimately right and worked perfectly. Could't expect less from the very creator of CsQuery (congrats! It's really an **awesome** work which I think should be a lot more known). With respect to your solution, I couldn't find any mention to the `.Cq()` method in the documentarion. (I did read about the `.Csq()` but it seems to be just an alias for exactly the same that I was doing.) – Sam Jan 09 '13 at 19:57
  • I feel terrible about the state of the docs - if it weren't for the fact that this more or less follows the jQuery API, I'd say it's a miracle anyone uses it :) `Csq` is wrong. I changed it to `Cq` a long time ago to match the name of the core object, sounds like I just didn't update the docs. I have the makings of a web site and most of the API is documented through the xml comments, I've just been short on time finish it up and get it online! – Jamie Treworgy Jan 09 '13 at 20:00
  • I can see that you are in fact a very good reader.. the docs are *completely wrong* and tell you to use Create! Fixing now. – Jamie Treworgy Jan 09 '13 at 20:02
  • Well, the docs may be outdated, but at least the library works nicely and seems very intuitive to someone familiar with jQuery. Anyway, I think that it would be quite more jQuery-like to have `CQ.csQuery(element);` as an alternative to `element.Cq()` since it evokes in a much familiar way to the typical `jQuery(element)` – Sam Jan 09 '13 at 20:08
  • What I REALLY wanted was a default static method of the sort that don't exist in C# so you could say `var cqElement = CQ(element)`. At one time I had an implicit constructor so you could say `CQ cqElement = element`. (You can do that with a string of html now, e.g. `CQ dom = "
    ...
    "`) It looks like I removed the one for element, not sure why, will have to look back into it. I may have been concerned about ambiguity of the sort we're discussing here.
    – Jamie Treworgy Jan 09 '13 at 20:15
  • I don't know if it could be considered a bad practice, but a solution I would do for that would creating a public method `S` with different overrides wich resembles jQuery's `$` (and also happens to be the first letter of the word "Selector", adding more mnemonics). Of course, it would work only when you need to work just with one DOM at a time. What I mean is something like this: http://pastebin.com/FDzzDBy6 – Sam Jan 09 '13 at 21:46