tag:blogger.com,1999:blog-28838805128306861492024-03-13T11:29:16.840-04:00OutsharkedJamiehttp://www.blogger.com/profile/04089108417465569092noreply@blogger.comBlogger44125tag:blogger.com,1999:blog-2883880512830686149.post-88536053762352961962019-10-08T15:01:00.002-04:002019-10-08T16:51:30.599-04:00WSL, Windows Terminal, almost bliss<i><span style="font-family: "arial" , "helvetica" , sans-serif;"><b>You thought this blog was dead? Ha! Never!</b></span></i><br />
<a href="https://1.bp.blogspot.com/-lt4gfLa99dQ/XZzVsEhCkTI/AAAAAAAAnuc/BE-m7pywLh8_6atSUCOM96bJovYTf7BBgCLcBGAsYHQ/s1600/nixieclock.jpg" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" data-original-height="792" data-original-width="1600" height="158" src="https://1.bp.blogspot.com/-lt4gfLa99dQ/XZzVsEhCkTI/AAAAAAAAnuc/BE-m7pywLh8_6atSUCOM96bJovYTf7BBgCLcBGAsYHQ/s320/nixieclock.jpg" width="320" /></a><span style="font-family: "arial" , "helvetica" , sans-serif;"></span><br />
<span style="font-family: "arial" , "helvetica" , sans-serif;"><span style="font-family: "arial" , "helvetica" , sans-serif;"><br /></span></span>
<span style="font-family: "arial" , "helvetica" , sans-serif;">I have to admit admit, using this "blogger" editor for the first time in several years feels pretty retro. It's the same feeling I got the last time I tried to use a rotary phone.</span><span style="font-family: "arial" , "helvetica" , sans-serif;"> I'm honestly a bit surprised everything is still as I left it so long ago, and it still seems to work. But hey, what's old is new/cool again, right? Or something. Yeah, the format is crap. I'm thinking about adding an "under construction" animated gif to round it out.</span><br />
<span style="font-family: "arial" , "helvetica" , sans-serif;"><br /></span>
<span style="font-family: "arial" , "helvetica" , sans-serif;">Anyway, I'm using a Windows machine full-time again after a couple years using a MacBook. This mostly makes me happy. Luckily, the worst thing about the Windows software developer experience - the wretched console/shell situation - has improved somewhat. Not completely there yet, but it was far easier to set myself up with a basically high-functioning linux shell for all my console keyboarding, and be able to use Visual Studio Code and so on. Here's what I did.</span><br />
<span style="font-family: "arial" , "helvetica" , sans-serif;"><br /></span>
<span style="font-family: "arial" , "helvetica" , sans-serif;">1) Set up <a href="https://docs.microsoft.com/en-us/windows/wsl/install-win10">WSL </a>(Windows Subsystem for Linux)</span><br />
<span style="font-family: "arial" , "helvetica" , sans-serif;">2) Install <a href="https://github.com/microsoft/terminal">Microsoft Windows Terminal</a>. The easiest way is with Chocolatey:</span><br />
<pre class="myprettyprint prettyprint">> choco install microsoft-windows-terminal
</pre>
<span style="font-family: "arial" , "helvetica" , sans-serif;"><br /></span><span style="font-family: "arial" , "helvetica" , sans-serif;">Note: Windows Terminal is definitely pre-beta software, but I've been using it a couple weeks without incident. While it lacks a lot of config options so far, it's fast, seems to avoid all the jank and complexity of ConEmu, and basically... just works.</span><br />
<span style="font-family: "arial" , "helvetica" , sans-serif;"><br /></span>
<span style="font-family: "arial" , "helvetica" , sans-serif;">3) Install <a href="https://github.com/flyingpie/windows-terminal-quake">windows-terminal-quake</a>, or my not-yet-mainlined fork <a href="https://github.com/jamietre/windows-terminal-quake">here</a>. This is a helper that adds a vital missing UX feature to Windows Terminal - hotkey access. CTRL+~ will open/close the terminal in quake style; or bring to the foreground if it's not. This will most likely go away soon; this is a <a href="https://github.com/microsoft/Terminal/issues/653">much requested feature</a> so it seems likely it will become part of WT before long.</span><br />
<span style="font-family: "arial" , "helvetica" , sans-serif;"><br /></span>
<span style="font-family: "arial" , "helvetica" , sans-serif;">4) Install <a href="https://github.com/robbyrussell/oh-my-zsh">oh-my-zsh</a> in your wsl environment:</span><br />
<pre class="myprettyprint prettyprint">> sudo apt install zsh
> ssh -c "$(curl -fsSL https://raw.githubusercontent.com/robbyrussell/oh-my-zsh/master/tools/install.sh)"
</pre>
<br />
<span style="font-family: "arial" , "helvetica" , sans-serif;"></span><span style="font-family: "arial" , "helvetica" , sans-serif;">5) Add windows-style CLI editing capability to your wsl zsh shell with <a href="https://gist.github.com/jamietre/65869c073119bb68f283e635cf6463b1">this gist</a>. This lets you use familiar windows keyboard combinations for editing inline - e.g. </span><span style="font-family: "courier new" , "courier" , monospace;">shift+left/right</span><span style="font-family: "arial" , "helvetica" , sans-serif;"> to select text, </span><span style="font-family: "courier new" , "courier" , monospace;">ctrl+shift+left/right</span><span style="font-family: "arial" , "helvetica" , sans-serif;"> to select by word, </span><span style="font-family: "courier new" , "courier" , monospace;">home/end</span><span style="font-family: "arial" , "helvetica" , sans-serif;"> to move to start or end of the line, etc.</span><br />
<span style="font-family: "arial" , "helvetica" , sans-serif;"><br /></span>
<span style="font-family: "arial" , "helvetica" , sans-serif;">6) If you want, change your keybindings in Windows Terminal so </span><span style="font-family: "courier new" , "courier" , monospace;">CTRL+V </span><span style="font-family: "arial" , "helvetica" , sans-serif;">is "paste." The default is </span><span style="font-family: "courier new" , "courier" , monospace;">CTRL+SHIFT+V.</span><span style="font-family: "arial" , "helvetica" , sans-serif;"> Since I rarely use the <a href="https://superuser.com/questions/421463/why-does-ctrl-v-not-paste-in-bash-linux-shell/421468">native bash </a></span><span style="font-family: "courier new" , "courier" , monospace;"><a href="https://superuser.com/questions/421463/why-does-ctrl-v-not-paste-in-bash-linux-shell/421468">CTRL+V</a></span><span style="font-family: "arial" , "helvetica" , sans-serif;"> this doesn't do much for me; I'd rather </span><span style="font-family: Courier New, Courier, monospace;">CTRL+V</span><span style="font-family: "arial" , "helvetica" , sans-serif;"> work the same everywhere. So just rebind it. :</span><br />
<pre class="myprettyprint prettyprint">{
"command": "paste",
"keys": [ "ctrl+v" ]
}
</pre>
<span style="font-family: "arial" , "helvetica" , sans-serif;"><br /></span><span style="font-family: "arial" , "helvetica" , sans-serif;"><i>But what about copy?</i> </span><span style="font-family: "courier new" , "courier" , monospace;">CTRL+C</span><span style="font-family: "arial" , "helvetica" , sans-serif;"> will already copy text when you are editing inline in zsh without any config changes to Windows Terminal if you did step #5. That script will automatically bind </span><span style="font-family: "courier new" , "courier" , monospace;">CTRL+C</span><span style="font-family: "arial" , "helvetica" , sans-serif;"> to copy the selected text in a command line to the Windows clipboard -- but <i>only </i>while zsh is within the interactive CLI.</span><br />
<span style="font-family: "arial" , "helvetica" , sans-serif;"><br /></span>
<span style="font-family: "arial" , "helvetica" , sans-serif;">When a command is running, </span><span style="font-family: "courier new" , "courier" , monospace;">CTRL+C</span><span style="font-family: "arial" , "helvetica" , sans-serif;"> will still send an interrupt and break the running program. This results in the very natural (to me) behavior of </span><span style="font-family: "courier new" , "courier" , monospace;">CTRL+C </span><span style="font-family: "arial" , "helvetica" , sans-serif;">copying when I expect it to, and breaking when I expect it to.</span><br />
<span style="font-family: "arial" , "helvetica" , sans-serif;"><br />You can also copy text from outside the command line using the mouse within Windows Terminal's native behavior. Just click-select something and right-click it, and it will be coped. WT handles pasting in every context; no special config needed here other than to change the keybinding.</span><span style="font-family: "arial" , "helvetica" , sans-serif;"><br /></span><br />
<span style="font-family: "arial" , "helvetica" , sans-serif;"><br /></span>
<span style="font-family: "arial" , "helvetica" , sans-serif;">That's about it! I can pretty much do everything I want: </span><br />
<ul>
<li><span style="font-family: "arial" , "helvetica" , sans-serif;">Use a real linux CLI environment</span></li>
<li><span style="font-family: "arial" , "helvetica" , sans-serif;">Copy/Paste work as expected</span></li>
<li><span style="font-family: "arial" , "helvetica" , sans-serif;">I can edit text in the interactive CLI as I would elsewhere in windows</span></li>
<li><span style="font-family: "arial" , "helvetica" , sans-serif;">Quake!</span></li>
<li><span style="font-family: "arial" , "helvetica" , sans-serif;">Just type `</span><span style="font-family: "courier new" , "courier" , monospace;">code .`</span><span style="font-family: "arial" , "helvetica" , sans-serif;"> to launch vscode to work in the current wsl directory</span></li>
</ul>
<div>
<span style="font-family: "arial" , "helvetica" , sans-serif;"><br /></span></div>
<br />
<span style="font-family: "arial" , "helvetica" , sans-serif;"><br /></span>
<span style="font-family: "arial" , "helvetica" , sans-serif;">- </span>Jamiehttp://www.blogger.com/profile/04089108417465569092noreply@blogger.com0tag:blogger.com,1999:blog-2883880512830686149.post-54554573193378950032015-06-12T09:21:00.000-04:002015-06-12T11:02:02.934-04:00Using Color Theme Editor in Visual Studio 2015 RC<span style="font-family: Arial, Helvetica, sans-serif;">For inexplicable reasons, Microsoft has put out a release candidate for Visual Studio 2015 without updating the color theme editor. </span><br />
<a href="http://www.roflcat.com/images/cats/U_Got_Da_Wrong_Colors_On_Fool.jpg" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" src="http://www.roflcat.com/images/cats/U_Got_Da_Wrong_Colors_On_Fool.jpg" height="225" width="320" /></a><span style="font-family: Arial, Helvetica, sans-serif;"><br /></span><br />
<span style="font-family: Arial, Helvetica, sans-serif;">This may seem like a minor point, but most developers I know are somewhat particular about their development environment. We go to great pains to make it look and feel <i>exactly</i> the way we want to. There are so many scenarios that can be configured. It can take a <i>long time</i> to get things just the way you like them.</span><br />
<span style="font-family: Arial, Helvetica, sans-serif;"><br /></span>
<span style="font-family: Arial, Helvetica, sans-serif;">So when I first fired up VS 2015 RC1 and discovered that it looked like Visual Studio 2010, and there was no theme editor extension, I just shut it down and figured I'd wait it out. I was using a custom theme as my starting point, and had heavily customized it from there. If it was even possible to replicate it without the theme editor, I didn't know where to begin.</span><br />
<span style="font-family: Arial, Helvetica, sans-serif;"><br /></span>
<span style="font-family: Arial, Helvetica, sans-serif;"><b>There is a solution.</b></span><br />
<span style="font-family: Arial, Helvetica, sans-serif;"><b><br /></b></span>
<span style="font-family: Arial, Helvetica, sans-serif;">Actually, it's one I've <a href="http://blog.outsharked.com/2012/09/using-your-favorite-visual-studio-2010.html">written about before.</a></span><br />
<br />
<span style="font-family: Arial, Helvetica, sans-serif;">You can use the VS2013 color theme editor extension in VS2015. It doesn't work perfectly, but I was able to migrate my settings from VS2013 without too much trouble, and once that's done you don't need to touch it again.</span><br />
<span style="font-family: Arial, Helvetica, sans-serif;"><br /></span>
<br />
<h4>
<span style="font-family: Arial, Helvetica, sans-serif;">1. Export settings</span></h4>
<span style="font-family: Arial, Helvetica, sans-serif;"><br /></span>
<span style="font-family: Arial, Helvetica, sans-serif;">Fire up VS2013. </span><br />
<div>
<a href="http://3.bp.blogspot.com/-aTGCRoJcA_0/VXrWLgUsfYI/AAAAAAAAGLg/KDGT3Uuko8g/s1600/Untitled.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" height="150" src="http://3.bp.blogspot.com/-aTGCRoJcA_0/VXrWLgUsfYI/AAAAAAAAGLg/KDGT3Uuko8g/s200/Untitled.png" width="200" /></a><br />
<ul>
<li><span style="font-family: Arial, Helvetica, sans-serif;">Choose <b>Tools -> Import and Export Settings</b>.</span></li>
<li><span style="font-family: Arial, Helvetica, sans-serif;">Select "Export selected environmental settings" and click <b>Next.</b></span></li>
<li><span style="font-family: Arial, Helvetica, sans-serif;"><i>Unselect everything</i> by clicking the partially-checked box next to All Settings.</span></li>
<li><span style="font-family: Arial, Helvetica, sans-serif;">Expand <b>Options, Environment</b> and check <b>Fonts and Colors</b>, and click <b>Next.</b></span></li>
<li><span style="font-family: Arial, Helvetica, sans-serif;">Change the target path if desired, and click <b>Finish</b> to export your color settings.</span></li>
</ul>
<div>
<span style="font-family: Arial, Helvetica, sans-serif;">If you are using a custom theme from the VS color theme editor, you need to export it too.</span></div>
<ul><a href="http://2.bp.blogspot.com/-iSx4z892qPU/VXrWwZnReEI/AAAAAAAAGLo/S4D6x0mS6y8/s1600/Untitled.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" height="65" src="http://2.bp.blogspot.com/-iSx4z892qPU/VXrWwZnReEI/AAAAAAAAGLo/S4D6x0mS6y8/s200/Untitled.png" width="200" /></a>
<li><span style="font-family: Arial, Helvetica, sans-serif;">Choose </span><b style="font-family: Arial, Helvetica, sans-serif;">Tools -> Customize Colors.</b><span style="font-family: Arial, Helvetica, sans-serif;"> </span></li>
<li><span style="font-family: Arial, Helvetica, sans-serif;">Select the them you are using. In my case it's a custom theme called Monokai. </span></li>
<li><span style="font-family: Arial, Helvetica, sans-serif;">Click the <b>Edit Theme</b> icon (the center one).</span></li>
<li><span style="font-family: Arial, Helvetica, sans-serif;">Click the "export" icon (circled in the picture to the right).</span></li>
<li><span style="font-family: Arial, Helvetica, sans-serif;">Change the file type to "Theme Package Definitions (*.pkgdef)" and save it somewhere. You're done with this part.</span></li>
</ul>
<div>
<span style="font-family: Arial, Helvetica, sans-serif;"><br /></span></div>
</div>
<h4>
<span style="font-family: Arial, Helvetica, sans-serif;">2. Migrate the extension</span></h4>
<div>
<span style="font-family: Arial, Helvetica, sans-serif;"><br /></span></div>
<div>
<span style="font-family: Arial, Helvetica, sans-serif;">Following the <a href="http://blog.outsharked.com/2012/09/using-your-favorite-visual-studio-2010.html">same instructions from two and a half years ago</a> that I wrote up to migrate Ultrafind, migrate the color theme editor extension to VS2015. This time around the source is 12.0, the target is 14.0, and the extension is in a folder called </span><span style="font-family: Courier New, Courier, monospace;">1rdkntxh.iwn</span><span style="font-family: Arial, Helvetica, sans-serif;"> (at least on my computer, it was pretty obvious which it was just by peeking in the folders). Copy this thing to the 14.0 folder.</span></div>
<div>
<span style="font-family: Arial, Helvetica, sans-serif;"><br /></span></div>
<div>
<span style="font-family: Arial, Helvetica, sans-serif;">If you exported a custom theme as I did above, copy the <b>.pkgdef</b> file you exported into the </span><span style="font-family: Courier New, Courier, monospace;">Colors</span><span style="font-family: Arial, Helvetica, sans-serif;"> subfolder where you copied the extension itself. This isn't where custom themes lived before, but rather promotes it to an "installed theme." This was necessary as the "import theme" feature doesn't seem to work properly under VS2015, however, just putting it there works fine.</span></div>
<div>
<span style="font-family: Arial, Helvetica, sans-serif;"><br /></span></div>
<div>
<h4>
<span style="font-family: Arial, Helvetica, sans-serif;">3. Enable it</span></h4>
</div>
<div>
<span style="font-family: Arial, Helvetica, sans-serif;"><br /></span></div>
<div>
<span style="font-family: Arial, Helvetica, sans-serif;">Start Visual Studio 2015, and choose <b>Tools -> Extensions and Updates.</b> Select "Visual Studio 2013 Color Theme Editor" and click <b>Enable. </b>Restart VS as directed.</span></div>
<div>
<span style="font-family: Arial, Helvetica, sans-serif;"><br /></span></div>
<div>
<h4>
<span style="font-family: Arial, Helvetica, sans-serif;">4. Import old settings</span></h4>
</div>
<div>
<span style="font-family: Arial, Helvetica, sans-serif;"><br /></span></div>
<div>
<span style="font-family: Arial, Helvetica, sans-serif;">The theme editor should load up right away. Select your theme of choice.</span></div>
<div>
<span style="font-family: Arial, Helvetica, sans-serif;"><br /></span></div>
<div>
<span style="font-family: Arial, Helvetica, sans-serif;">Now import your general fonts & color settings that you previously exported using <b>Tools -> Import and Export Settings</b></span></div>
<div>
<span style="font-family: Arial, Helvetica, sans-serif;"><br /></span></div>
<div>
<h4>
<span style="font-family: Arial, Helvetica, sans-serif;">5. Awesome! Now I can function again. </span></h4>
</div>
<h3>
</h3>
<h3>
<span style="font-family: Arial, Helvetica, sans-serif;">... so does it work for other stuff I can't live without too?</span></h3>
<div>
<span style="font-family: Arial, Helvetica, sans-serif;"><br /></span>
<span style="font-family: Arial, Helvetica, sans-serif;">Maybe?</span></div>
<div>
<span style="font-family: Arial, Helvetica, sans-serif;"><br /></span></div>
<div>
<span style="font-family: Arial, Helvetica, sans-serif;">I tried <b>Productivity Power Tools</b> and it crashed as soon as I loaded a project. </span><span style="font-family: Arial, Helvetica, sans-serif;"> Oh well.</span></div>
<div>
<span style="font-family: Arial, Helvetica, sans-serif;"><br /></span>
<span style="font-family: Arial, Helvetica, sans-serif;">As for Ultrafind? After it disappeared of the face of the earth a couple years ago, I eventually found a great alternative called <a href="http://entrian.com/source-search/">Entrian Source Search</a> -- not free but well worth the $29 price. In the year or so since I've been using it, it's probably cost me about $0.000001 per use. It's indispensible. </span><br />
<span style="font-family: Arial, Helvetica, sans-serif;"><br /></span>
<span style="font-family: Arial, Helvetica, sans-serif;">Here's hoping this gets a VS2015 update soon.</span><br />
<span style="font-family: Arial, Helvetica, sans-serif;"><br /></span>
</div>
<div>
<br /></div>
Jamiehttp://www.blogger.com/profile/04089108417465569092noreply@blogger.com5tag:blogger.com,1999:blog-2883880512830686149.post-27093266199633604742013-05-15T12:25:00.000-04:002014-08-28T14:29:58.865-04:00Adding "enter key" handling to forms using Knockout.js & jQuery<p>Been a long time since my last post here, busy year! And man does this blog layout look dated.. priorities... </p>
<p>Here's a quick and easy solution to a problem for which I didn't find a complete solution elsewhere. A nice input form UI will let the user press the ENTER key to submit the form. Browsers don't have a predictable and consistent handling of this action, especially if there's more than one form on a page. Furthermore, if you aren't actually using a <code>submit</code> type element, browsers won't do anything as they have no way of knowing the intent anyway.
</p>
<p>This extender for knockout.js + jQuery adds this functionality. All you need to do is add a binding for <code>enterkey</code> to an element that wraps your form, and pressing enter on any input control that's a descendent of the element containing the binding will cause that controller method to be invoked.
</p>
<b>Example knockout bindings:</b>
<pre class="myprettyprint prettyprint">
<div id="form1" data-bind="enterkey: submit">
Name: <input type=text data-bind="value: fullname">
... more fields ...
<a href="#" data-bind="click: submit">Save</a>
</div>
<script type="text/javascript">
var model = {
fullname: ko.observable(),
// ... more fields
submit: function() {
// process the form
}
};
ko.applyBindings(model);
</script>
</pre>
<b>The code:</b>
<pre class="myprettyprint prettyprint">
ko.bindingHandlers.enterkey = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
var allBindings = allBindingsAccessor();
$(element).on('keypress', 'input, textarea, select', function (e) {
var keyCode = e.which || e.keyCode;
if (keyCode !== 13) {
return true;
}
var target = e.target;
target.blur();
allBindings.enterkey.call(viewModel, viewModel, target, element);
return false;
});
}
}
};
</pre>
See an <a target="_blank" href="http://jsfiddle.net/jamietre/VTQQA/">example on JsFiddle</a>Jamiehttp://www.blogger.com/profile/04089108417465569092noreply@blogger.com6tag:blogger.com,1999:blog-2883880512830686149.post-87726538077649797302012-10-16T13:53:00.000-04:002013-01-04T14:30:28.753-05:00CsQuery 1.3 Released<p><b>CsQuery 1.3 has been released.</b> You can get it from <a href="http://www.nuget.org/packages/CsQuery">NuGet</a> or from the source repository on <a href="http://www.github.com/jamietre/csquery">GitHub</a>.
</p>
<h4>New HTML5 Compliant Parser</h4>
<p>
This release replaces the original HTML parser with the <a target="_blank" href="http://about.validator.nu/htmlparser/">validator.nu HTML5 parser.</a> This is a <i>complete, standards-compliant HTML5 parser.</i> This is the same codebase used in gecko-based web browsers (e.g. Firefox). You should expect excellent compatibility with the DOM that a web browser would render from markup. Problems that people have had in the past related to character set encoding, invalid HTML parsing, and other edge cases should simply go away.
</p>
<p>
In the process of implementing the new parser, some significant changes were made to the input and output API in order to take advantage of the its capabilities. While these revisions are generally backwards compatible with 1.2.1, there are a few potentially breaking changes. These can be summarized as follows:
<ul>
<li><b><code>DomDocument.DomRenderingOptions</code> has been removed.</b> The concept of assigning output options to a Document doesn't make sense any more (if it ever did); rather, you define options for how output is rendered at the time you render it.</li>
<li><b><code>IOutputFormatter</code> interface has changed.</b> This wasn't really used for anything before, so I doubt this will impact anyone, but it's conceivable that someone coded against it. The interface has been revised somewhat, and it is now used extensively to define a model for rendering output.</li>
</ul>
</p>
<p>
Hopefully, these changes won't impact you much or at all. But with this small price comes a host of new options for parsing and rendering HTML.
</p>
<h4>Create Method Options</h4>
<ul><li>Complete documentation: <a target="_blank" href="https://github.com/jamietre/CsQuery/blob/master/documentation/create.md">Create method</a></li></ul>
<p><big>In the beginning,</big> there was but a single way to create a new DOM from HTML: <code>Create</code>. <i>And it was good.</i> But as the original parser evolved towards HTML5 compliance, the <code>CreateFragment</code> and <code>CreateDocument</code> methods were added, to define intent. Different rules apply depending on the context: a full document must always have an <code>html</code> tag (among others) for example. But you wouldn't want to add any missing tags if your intent was to create a fragment that was not supposed to stand alone.</p>
<p>
The new parser has some more toys. It lets us define an expected document type (HTML5, HTML4 Strict, HTML4 Tranistional). We can tell it the context we expect out HTML to be found in when it starts parsing. We can choose to discard comments, and decide to permit self-closing XML tags. All of these things went into the <code>Create</code> method, allowing you complete control over how your input gets processed.
</p>
<h3>New Overloads</h3>
<p>
The basic <code>Create</code> method has overloads to accept a number of different kinds of input:
<pre class="myprettyprint prettyprint">
public static CQ Create(string html)
public static CQ Create(char[] html)
public static CQ Create(TextReader html)
public static CQ Create(Stream html)
public static CQ Create(IDomObject element)
public static CQ Create(IEnumerable<IDomObject> elements)
</pre>
<p>
Additionally, there are similar overloads with parameters that let you control each option:
<pre class="myprettyprint prettyprint">
public static CQ Create(string html,
HtmlParsingMode parsingMode =HtmlParsingMode.Auto,
HtmlParsingOptions parsingOptions = HtmlParsingOptions.Default,
DocType docType = DocType.Default)
</pre>
<p>
When calling the basic methods, the "default" values of each of these will be used. The default values are defined on the <code>CsQuery.Config</code> object (the "default defaults" are shown here -- if you change these on the config object, your new values will be used whenever a default is requested):
<pre class="myprettyprint prettyprint">
CsQuery.Config.HtmlParsingOptions = HtmlParsingOptions.None;
CsQuery.Config.DocType = DocType.HTML5;
</pre>
Note that <code>HtmlParsingOptions</code> is a <code>[Flags]</code> enum. This means you can specify more than one option. So you could, for example, call Create like this:
<pre class="myprettyprint prettyprint">
var dom = CQ.Create(someHtml,HtmlParsingOptions.Default | HtmlParsingOptions.IgnoreComments);
</pre>
<p>If you pass a method <i>both</i> Default <i>and</i> some other option(s), it will merge the default values with any additional options you specified. On the other hand, passing options that do not include Default will result in only the options you passed being used.
<p>The other methods remain more or less unchanged. <code>CreateDocument</code> and <code>CreateFragment</code> now simply call <code>Create</code> using the appropriate <code>HtmlParsingOption</code> to define the intended document type.
<pre class="myprettyprint prettyprint">
public static CQ CreateDocument(...)
public static CQ CreateFragment(...)
public static CQ CreateFromFile(...)
public static CQ CreateFromUrl(...)
public static CQ CreateFromUrlAsync(...)
</pre>
<p>The <code>Create</code> method offers a wide range of options for input and parsing. These other methods were created for convenience and before an API to handle input features had been thought out. Though I don't intend to deprecate them right away, I will not likely extend them to support the various options. Anything you can do with these methods can be done about as easily with `Create` and a helper of some kind. For example, if you want to load a DOM from a file using options other than the defaults, you can just pass `File.Open(..)` to the standard `Create` method.
<h4>Render Method Options</h4>
<ul><li>Complete documentation: <a target="_blank" href="https://github.com/jamietre/CsQuery/blob/master/documentation/render.md">Render method</a></li></ul>
<p>
The Render method signatures look pretty much the same as 1.2.1.. but a lot has changed behind the scenes. The <code>IOutputFormatter</code> interface, which used to be more or less a placeholder, now runs the show. All output is controlled by OutputFormatters implementing this interface. Any <code>Render</code> method which doesn't explicitly identify an OutputFormatter will be using the default formatter provided by the service locator <code>CsQuery.Config.GetOutputFormatter</code>.
<pre class="myprettyprint prettyprint">
public static Func<IOutputFormatter> GetOutputFormatter {get;set;}
</pre>
You can replace the default locator with any delegate that returns <code>IOutputFormatter.</code>. Additionally, you can assign a single instance of a class to the <code>CsQuery.Config.OutputFormatter</code> property, which, if set, will supercede use of service locator. When using this method, the object <i>must</i> be thread safe, since new instances will not be created for each use.
<p>There are a number of built-in <code>IOutputFormatter</code> objects accessible through the static <code>OutputFormatters</code> factory:
<pre class="myprettyprint prettyprint">
OutputFormatters.HtmlEncodingBasic
OutputFormatters.HtmlEncodingFull
OutputFormatters.HtmlEncodingMinimum
OutputFormatters.HtmlEncodingMinimumNbsp
OutputFormatters.HtmlEncodingNone
OutputFormatters.PlainText
</pre>
<p>Each of these except the last returns an OutputFormatter configured with a particular HtmlEncoder. The last strips out HTML and returns just the text contents (to the best of its ability). The factory also has <code>Create</code> methods that let you configure it with specific <code>DomRenderingOptions</code> too. Complete details of these options are in the <a target="_blank" href="https://github.com/jamietre/CsQuery/blob/master/documentation/render.md">Render method</a> documentation.
<h4>Bug Fixes</h4>
<ul><li><a href="https://github.com/jamietre/CsQuery/issues/51">Issue #51</a>: Fix an issue with compound subselectors whose target included CSS matches above the level of the context.
<li>Fix for <code>:empty</code> could return false when non-text or non-element nodes are present
</ul>
<br>
<h4>Other New Features</h4>
<p>The completely new HTML parser, input and output models aren't enough for you? Well, there are a couple other minor new features.
<ul><li>CsQuery should compile under Mono now, after implementing a suggestion to change to `CsQuery.Utility.JsonSerializer.Deserialize` to avoid an unimplemented Mono framework feature.
<li>Added a <code>HasAttr</code> method to test for the presence of a named attribute.
<li>Add CSS descriptor for Paged Media Module per <a href="https://github.com/jamietre/CsQuery/pull/40">Pull Request #40</a> from @kaleb
<li>`CQ.DefaultDocType` has been marked as obsolete and will be removed in a future version. Use `Config.DocType` instead
<li>`CQ.DefaultDomRenderingOptions` has been marked as obsolete and will be removed in a future version. Use `Config.DomRenderingOptions` instead.
</ul>
<p>There are other changes in the <a target="_blank" href="https://github.com/jamietre/CsQuery/blob/master/source/README.md">complete change log</a>, however, many of them are related to the deprecated parser and no longer relevant.
<br>
<h4>Thanks To The Community</h4>
<p>
This is a big project, and the new parser is a huge step forward. I think you'll find this release is fast, stable, flexible, and standards-compliant. I owe a debt to a number of people who suffered through the development and beta releases for the last couple months, without their patience and feedback, this would not have been possible. <i><a target="_blank" href="http://www.hanselman.com/blog/ABugReportIsAGift.aspx">A bug report is a gift!</a></i> So thanks to all the givers. The following is a list of all the people who've contributed code or bug reports recently. (If I missed anyone, it wasn't intentional!) Thanks - please keep it coming.
<p>
<a target="_blank" href="https://github.com/Vitallium">Vitallium</a> (code),
<a target="_blank" href="https://github.com/kaleb">kaleb</a> (code),
<a target="_blank" href="https://github.com/petterek">petterek</a>,
<a target="_blank" href="https://github.com/ilushka85">ilushka85</a>,
<a target="_blank" href="https://github.com/laurentlbm">laurentlbm</a>,
<a target="_blank" href="https://github.com/martincarlsson">martincarlsson</a>,
<a target="_blank" href="https://github.com/allroadcole">allroadcole</a>,
<a target="_blank" href="https://github.com/Nico1234">Nico1234</a>,
<a target="_blank" href="https://github.com/Uncleed">Uncleed</a>,
<a target="_blank" href="https://github.com/Vids">Vids</a>,
<a target="_blank" href="https://github.com/Arithmomaniac">Arithmomaniac</a>,
<a target="_blank" href="https://github.com/CJCannon">CJCannon</a>,
<a target="_blank" href="https://github.com/CJCannon">muchio7</a>,
<a target="_blank" href="https://github.com/CJCannon">SaltyDH</a>
<p>
<hr />
<p>
<i>CsQuery is a complete CSS selector engine and jQuery port for .NET4 and C#. It's on NuGet as <a href="http://www.nuget.org/packages/CsQuery">CsQuery</a>. For documentation and more information please see the <a href="http://www.github.com/jamietre/csquery">GitHub repository</a> and <a href="http://blog.outsharked.com/search/label/csquery">posts about CsQuery</a> on this blog.
</i></p>Jamiehttp://www.blogger.com/profile/04089108417465569092noreply@blogger.com0tag:blogger.com,1999:blog-2883880512830686149.post-53774319247854582032012-09-13T10:07:00.002-04:002012-09-16T08:24:06.139-04:00Using your favorite Visual Studio 2010 add-ins/extensions in VS2012<p>I've just about finished my transition from Visual Studio 2010 to Visual Studio 2012. While this has probably been the easiest of any VS update I can remember, it wasn't without a few painful moments. Here's a summary of the annoyances and the solutions I found.
<h4>Uppercase Menus</h4>
<p>Why, Microsoft, why? I don't want my menus to shout at me. It just looks so... 1992. Luckily, the fix is a <a target="_blank" href="http://blogs.msdn.com/b/zainnab/archive/2012/06/14/turn-off-the-uppercase-menu-in-visual-studio-2012.aspx">piece of cake</a> and requires adding a registry key:
<p><code>[HKEY_CURRENT_USER\Software\Microsoft\VisualStudio\11.0\General]
"SuppressUppercaseConversion"=dword:00000001
</code>
<p>Or just run this to add it automatically: <a href="http://www.outsharked.com/blog/vs_menu_case.reg">vs_menu_case.reg</a>
<h4>Impenetrable color themes</h4>
<p>Metro has is moments, but eliminating any visual distinction between windows, boundaries, and areas is not one of them. Neither of the two themes that come with VS2012 were especially workable for me.
<p>
<a target="_blank" href="http://visualstudiogallery.msdn.microsoft.com/366ad100-0003-4c9a-81a8-337d4e7ace05">Visual Studio 2012 Color Theme Editor</a> to the rescue. The "blue" theme that comes packaged with this painless extension is comfortingly familiar to those used to VS2010's default scheme. Hooray! I can find the edge of a window again.
<h4>Ultrafind (and other non-updated VS2010 extensions)</h4>
<p>Did you use <a target="_blank" href="http://visualstudiogallery.msdn.microsoft.com/9fa9fdd7-1c06-45e3-a9f3-0381caab8f94">Ultrafind</a> with VS2010? If no, I feel sorry for you. If yes, you probably miss it now, since it hasn't been updated.
<p>Not content to wait for an update, I threw caution to the wind and figured I'd see what happens if I just shoehorned it into VS2012. What do you know-- it works. Here's how to get your VS2010 extensions running in VS2012. <i><b>Warning:</b> I know nothing about what, if any, differences there may be in the extension model from VS2010 to VS2012. This works for me. It's absolutely not guaranteed to work for you or for all extensions, but there's not likely much harm you can do.</i>
<h2>1. Locate your VS2010 user extensions folder.</h2>
<p>
Start by opening up <code>C:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE\devenv.pkgdef</code> which shows you the locations from where extensions are loaded. Anything you've installed will likely be in "UserExtensionsFolder":
<p><code>"UserExtensionsRootFolder" = "$AppDataLocalFolder$\Extensions"</code>
<p>This is probably located here:
<p><code>C:\Users\{username}\AppData\Local\Microsoft\VisualStudio\10.0\Extensions</code>
<h2>2. Copy them.</h2>
<p>Within this folder should be a subfolder for each extension you've installed. Copy just the folders related to the extensions you want to migrate from here to the same folder for VS2012 -- the same path, but with "11.0" instead of "10.0". For Ultrafind, it's <code>Logan Mueller [MSFT]</code>.
<h2>3. Clear cache.</h2>
<p>There are two ".cache" files in the Extensions folder. Just delete them. This step might not be needed; I tried this a couple times with and without. If you don't do it, VS seems to get confused about which extensions are enabled. If you do, you may need to re-enable other extensions that are installed.
<h2>3. Enable.</h2>
<p><a href="http://1.bp.blogspot.com/-61mzOodfbf8/UFHhmGOZTjI/AAAAAAAAAJg/RP7UmijuOXk/s1600/ultrafind.gif" imageanchor="1" style="display:block; float:right; margin-left:1em; margin-bottom:1em"><img border="0" height="59" width="400" src="http://1.bp.blogspot.com/-61mzOodfbf8/UFHhmGOZTjI/AAAAAAAAAJg/RP7UmijuOXk/s400/ultrafind.gif" /></a>
You should now just be able to restart VS2012 and see your extension in the extension manager. Cross you fingers and click Enable.
<br />
<br />
<br />
<h4>Build Version Increment (and other add-ins)</h4>
<p>You can use a similar technique for add-ins. It's even easier.
The one I really care about is <a href="http://autobuildversion.codeplex.com/releases/view/60932">Build Version Increment</a>, which seems even less likely that Ultrafind to get an update any time soon (since it was barely updated for VS2010!).
<h2>1. Find the add-ins folder.</h2>
<p>Go to <b>Tools->Options->Add In Security</b> from within VS to find the add-in search path. (I happen to keep mine in dropbox so they stay in sync across several machines). If you've never touched this, your add-ins are probably located in <code>%VSMYDOCUMENTS%\Addins</code>, which is here:
<p><code>C:\Users\{username}\Documents\Visual Studio 2010\Addins</code>
<p>I have no idea why it's in a completely different place than extensions. Never question Microsoft logic.
<h2>2. Copy files</h2>
<p>Like before, just copy the files related to the addins you want to migrate to the same folder for VS 2012. It could be just a single file called "something.addin". For BuildVersionIncrement there's also a DLL.
<h2>3. Update version.</h2>
<p>Edit the "*.addin" file and look for this section:
<pre class="myprettyprint prettyprint">..
<HostApplication>
<Name>Microsoft Visual Studio</Name>
<Version>10.0</Version>
</HostApplication>
..
</pre>
<p>Just change that "10.0" to "11.0" and save. That's all. Restart visual studio. If the add-in isn't immediately available, go to <b>Tools->Add In Manager</b> and it should be listed; you can enable it there.
<a href="http://blog.outsharked.com" rel="tag" style="display:none;">CodeProject</a>Jamiehttp://www.blogger.com/profile/04089108417465569092noreply@blogger.com3tag:blogger.com,1999:blog-2883880512830686149.post-54480414792064377352012-08-13T14:40:00.001-04:002012-08-13T15:59:49.749-04:00jQuery :text filter selector deprecated in 1.8<h3>... and why it matters</h3>
<p>In the list of things changed for <a target="_blank" href="http://blog.jquery.com/2012/08/09/jquery-1-8-released/">jQuery 1.8</a>, you might miss this one, buried deep in the change log:</p>
<code><a target="_blank" href="http://bugs.jquery.com/ticket/9400">#9400</a>: Deprecate :text, :radio, :checkbox, etc. selector extensions</code>
<p>Sure enough.. it's got the <a target="_blank" href="http://api.jquery.com/text-selector/">scarlet letter "Deprecated" tag</a>. <b>What the...?</b> These jQuery pseudo-selectors are probably the first thing I ever learned about using jQuery. This seems to be a... confusing move at best.</p>
<p>Most of these jQuery extension selectors are easily replaced using longform CSS. Indeed, this is the rationale presented with the original request: they're redundant. For example, <code>:checkbox</code> is literally the same as <code>input[type=checkbox]</code>. While I've always like the terseness of the jQuery aliases, I could live without them.
<p>The problem is specifically with <code>:text</code> selector. The CSS version <code>input[type=text]</code> <i>does not work the same</i> as the jQuery <code>:text</code> selector. This is because when there's no <code>type</code> attribute, <code>:text</code> will select it, and the CSS version will not. CSS works only against actual attributes in the markup. This is important with "text" inputs because "text" is the default value. It's perfectly legal, valid, and even encouraged by some (because it's terse), to omit the "type" attribute for the ubiquitous text input. The simplest possible text input is just <code><input /></code>.
<p> Behold, a textbox, which you will then style with jQuery...
<br />
<div style="padding: 12px; border: 1px solid black;">
Text: <input /> <input type="checkbox"> Check if you love koala bears
</div>
<b>or NOT, since you can't <a id="select-text" href="#" onclick="javascript:$('input:text').css({border: '1px solid red', backgroundColor: '#dddddd'}); return false;" >select it</a> without :text!!!</b>
<p>Okay, this is not the end of the world. "Deprecated" is a lot different from "removed." jQuery contains features that were deprecated years ago, and it's not especially likely that this is going to be removed any time soon. But most people are uncomfortable writing new code that uses features they know are slated for future removal. So, starting with jQuery 1.8, you need to either choose to <i>always</i> have a "type" attribute, even though it's not required, or use a feature that's been deprecated to select all "text" inputs.
<p>So, this post is mostly an observation. If at some point in the future <code>:text</code> stopped working, you could always use a simple plugin to replace it. No big deal. But it's certainly a curious feature to remove. It's at the core of jQuery's original purpose: making it easy to work with HTML; filling the void left by the DOM and CSS. The <code>:text</code> filter clearly fills such a void; this change undoes something useful.
Jamiehttp://www.blogger.com/profile/04089108417465569092noreply@blogger.com3tag:blogger.com,1999:blog-2883880512830686149.post-31373156854951377562012-08-08T17:08:00.000-04:002012-08-20T13:04:12.842-04:00CsQuery 1.2 Released<p><b>CsQuery 1.2 has been released.</b> You can get it from <a href="http://www.nuget.org/packages/CsQuery">NuGet</a> or from the source repository on <a href="http://www.github.com/jamietre/csquery">GitHub</a>.
</p>
<p>This release does not add any significant new features, but is tied with the first formal release of the <a target="_blank" href="https://github.com/jamietre/CsQuery/tree/master/source/CsQuery.Mvc">CsQuery.Mvc</a> framework. This framework simplifies integrating CsQuery into an MVC project by allowing you to intercept the HTML output of a view before it's rendered and inspect or alter it using CsQuery. Additionally, it adds an HtmlHelper method for CsQuery so you can create HTML directly in Razor views. It's on nuget as <code>CsQuery.Mvc</code>.
</p>
<h3>Breaking Change</h3>
<p>Though this change is unlikely to affect many people, it is a significant change to the public API for DOM element creation. Any code which creates DOM elements using "new" such as:
<pre class="myprettyprint prettyprint">
IDomElement obj = new DomElement("div");
</pre>
will not compile, and should be replaced with:
<pre class="myprettyprint prettyprint">
IDomElement obj = DomElement.Create("div");
</pre>
<p>
This was necessary to support a derived object model for complex HTML Element implementations to better implement the browser DOM. Previously, any element-type specific functionality was handled conditionally. This was OK when the DOM model was mostly there to support a jQuery port, but as I have worked to create a more accurate representation of the browser DOM itself, it became clear this was not sustainable going forward
</p>
<p>In the new model, some DOM element types will be implemented using classes that derive from <code>DomElement</code>. This means that creating a new element must be done from a factory so that element types which have more specific implementations will be instances of their unique derived type.
</p>
<p>Any code that used <code>CQ.Create</code> or <code>Document.CreateElement</code> will be unaffected: this will only be a problem if you had been creating concrete <code>DomElemement</code> instances using <code>new.</code>
<h3>Bug Fixes</h3>
<p>
<ul><li><a target="_blank" href="https://github.com/jamietre/CsQuery/issues/27">Issue #27</a> - .Value for some HTML tags not implemented</li></ul>
</p>
<h3>CsQuery.Mvc</h3>
<p>As usual I'm behind on documentation, but the usage of CsQuery.Mvc is simple and there's an example MVC3 project in the github repo.
</p>
<p>
The CsQuery MVC framework lets you directly access the HTML output from an MVC view. It adds a property <code>Doc</code> to the controller and methods <code>Cq_ActionName</code> that run concurrently with action invocations, letting you manipulate the HTML via CsQuery before it's rendered. There's basic documentation <a target="_blank" href="https://github.com/jamietre/CsQuery/blob/master/source/CsQuery.Mvc/README.md">in the readme</a> and there's also an example MVC application showing how to use it. You can also take a look at the <a target="_blank" href="https://github.com/jamietre/CsQuery/tree/master/source/CsQuery.Mvc.Tests">CsQuery.Mvc.Tests</a> project which is, itself, an MVC application.
</p>
<p>Using the CsQuery HTML helper requires adding a reference to <code>CsQuery.Mvc</code> in <code>Views/web.config</code> as usual for any HtmlHelper extension methods:
<pre class="myprettyprint prettyprint">
<system.web.webPages.razor>
<host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
<pages pageBaseType="System.Web.Mvc.WebViewPage">
<namespaces>
<add namespace="System.Web.Mvc" />
<add namespace="System.Web.Mvc.Html" />
<add namespace="System.Web.Mvc.Ajax" />
<add namespace="System.Web.Routing" />
<b><add namespace="CsQuery.Mvc"/></b>
</namespaces>
</pages>
</system.web.webPages.razor>
</pre>
Now you can do this in a Razor view:
<pre class="myprettyprint prettyprint">
@Html.HtmlTag("div").AddClass("someclass").Text("some text");
</pre>
.. or anything at all that you can do with CsQuery normally, and the HTML output of the CQ object will be inserted inline.
<hr />
<p>
<i>CsQuery is a complete CSS selector engine and jQuery port for .NET4 and C#. It's on NuGet as <a href="http://www.nuget.org/packages/CsQuery">CsQuery</a>. For documentation and more information please see the <a href="http://www.github.com/jamietre/csquery">GitHub repository</a> and <a href="http://blog.outsharked.com/search/label/csquery">posts about CsQuery</a> on this blog.
</i></p>Jamiehttp://www.blogger.com/profile/04089108417465569092noreply@blogger.com0tag:blogger.com,1999:blog-2883880512830686149.post-75304326686591320772012-07-10T17:01:00.000-04:002012-08-09T03:09:43.650-04:00CsQuery 1.1.3 Released<script type="text/javascript" src="http://www.outsharked.com/scripts/jquery.accordion.js"></script>
<script type="text/javascript">
$(document).ready(function() {
$('body').accordion();
});
</script>
<style type="text/css">
.active
{
background-image: url('http://www.outsharked.com/images/icons/down-triangle.gif');
background-repeat:no-repeat;
background-position: 0px 12px;
padding-left: 20px;
}
.inactive
{
background-image: url('http://www.outsharked.com/images/icons/right-triangle.gif');
background-repeat:no-repeat;
background-position: 0px 4px;
padding-left: 20px;
}
ul.accordion
{
list-style-type: none;
padding:0;
margin:0;
}
ul.accordion ul
{
list-style-type: disc;
}
ul.accordion li
{
display: block;
clear:both;
padding-top: 6px;
padding-left: 16px;
margin:0;
}
</style>
<p><b>CsQuery 1.1.3 has been released.</b> You can get it from <a href="http://www.nuget.org/packages/CsQuery">NuGet</a> or from the source repository on <a href="http://www.github.com/jamietre/csquery">GitHub</a>.
</p>
<h3>New features</h3>
<p>
This release adds an API for extending the selector engine with custom pseudo-class selectors. In jQuery, you can do this with code <a href="http://james.padolsey.com/javascript/regex-selector-for-jquery/">like James Padolsey's <code>:regex</code> extension.</a>. In C#, we can do a little better than this since we have classes and interfaces to make our lives easier. To that end, in the <code>CsQuery.Engine</code> namespace, you can now find:
<pre>
interface IPseudoSelector
IPseudoSelectorFilter
IPseudoSelectorChild
abstract class PseudoSelector: IPseudoSelector
PseudoSelectorFilter: IPseudoSelectorFilter
PseudoSelectorChild: IPseudoSelectorChild
</pre>
<p>
The two different incarnations of the base IPseudoSelector interface represent two different types of pseudoclass selectors, which jQuery calls <a href="http://api.jquery.com/category/selectors/basic-filter-selectors/">basic filters</a> and <a href="http://api.jquery.com/category/selectors/child-filter-selectors/">child filters.</a> Technically there are also <a href="http://api.jquery.com/category/selectors/content-filter-selector/">content filters</a> but these work the same way as "basic filters" in practice.
</p>
<p>
If you are only testing characteristics of the element itself, then use a filter-type selector. If an element's inclusion in a set depends on its children (such as <code>:contents</code>, which tests text-node children) or depends on its position in relation to its siblings (such as <code>nth-child</code>) then you should probably use a child-type selector. In many cases you could do it either way. For example, <code>nth-child</code> could be implemented by looking at each element's <code>ElementIndex</code> property and figuring out if it's a match. But it would be much more efficient to start from the parent, and handpick each child that's at the right position.
</p>
<h3>The basic API</h3>
<p>To create a new filter, implement one of the two interfaces. They both share IPseudoSelector:
<ul id="accordion" class="accordion">
<li class="inactive">
<a href="#example"><h3>IPseudoSelector Interfaces</h3></a>
<div>
<pre class="prettyprint myprettyprint">
public interface IPseudoSelector
{
string Arguments { get; set; }
int MinimumParameterCount { get; }
int MaximumParameterCount { get; }
string Name { get; }
}
</pre>
<p>In both cases, you should set the min/max values to the number of parameters you want your filter to accept (the default is 0). "Name" should be the name of this filter as it will be used in a selector. Then choose the one that works best for your filter: </p>
<pre class="prettyprint myprettyprint">
public interface IPseudoSelectorChild : IPseudoSelector
{
bool Matches(IDomObject element);
IEnumerable<IDomObject> ChildMatches(IDomContainer element);
}
public interface IPseudoSelectorFilter: IPseudoSelector
{
IEnumerable<IDomObject> Filter(IEnumerable<IDomObject> selection);
}
</pre>
</div></li>
<li>
<a href="#example"><h3>PseudoSelector Abstract Class</h3></a>
<div>
<pre class="prettyprint myprettyprint">
/// <summary>
/// Base class for any pseudoselector that implements validation of min/max parameter values, and
/// argument validation. When implementing a pseudoselector, you must also implement an interface for the type
/// of pseudoselector
/// </summary>
public abstract class PseudoSelector : IPseudoSelector
{
#region private properties
private string _Arguments;
/// <summary>
/// Gets or sets criteria (or parameter) data passed with the pseudoselector
/// </summary>
protected virtual string[] Parameters {get;set;}
/// <summary>
/// A value to determine how to parse the string for a parameter at a specific index.
/// </summary>
///
/// <param name="index">
/// Zero-based index of the parameter.
/// </param>
///
/// <returns>
/// NeverQuoted to treat quotes as any other character; AlwaysQuoted to require that a quote
/// character bounds the parameter; or OptionallyQuoted to accept a string that can (but does not
/// have to be) quoted. The default abstract implementation returns NeverQuoted.
/// </returns>
protected virtual QuotingRule ParameterQuoted(int index)
{
return QuotingRule.NeverQuoted;
}
#endregion
#region public properties
/// <summary>
/// This method is called before any validations are called against this selector. This gives the
/// developer an opportunity to throw errors based on the configuration outside of the validation
/// methods.
/// </summary>
///
/// <value>
/// The arguments.
/// </value>
public virtual string Arguments
{
get
{
return _Arguments;
}
set
{
string[] parms=null;
if (!String.IsNullOrEmpty(value))
{
if (MaximumParameterCount > 1 || MaximumParameterCount < 0)
{
parms = ParseArgs(value);
}
else
{
parms = new string[] { ParseSingleArg(value) };
}
}
ValidateParameters(parms);
_Arguments = value;
Parameters = parms;
}
}
/// <summary>
/// The minimum number of parameters that this selector requires. If there are no parameters, return 0
/// </summary>
///
/// <value>
/// An integer
/// </value>
public virtual int MinimumParameterCount { get { return 0; } }
/// <summary>
/// The maximum number of parameters that this selector can accept. If there is no limit, return -1.
/// </summary>
///
/// <value>
/// An integer
/// </value>
public virtual int MaximumParameterCount { get { return 0; } }
/// <summary>
/// Return the properly cased name of this selector (the class name in non-camelcase)
/// </summary>
public virtual string Name
{
get
{
return Utility.Support.FromCamelCase(this.GetType().Name);
}
}
#endregion
#region private methods
/// <summary>
/// Parse the arguments using the rules returned by the ParameterQuoted method.
/// </summary>
///
/// <param name="value">
/// The arguments
/// </param>
///
/// <returns>
/// An array of strings
/// </returns>
protected string[] ParseArgs(string value)
{
List<string> parms = new List<string>();
int index = 0;
IStringScanner scanner = Scanner.Create(value);
while (!scanner.Finished)
{
var quoting = ParameterQuoted(index);
switch (quoting)
{
case QuotingRule.OptionallyQuoted:
scanner.Expect(MatchFunctions.OptionallyQuoted(","));
break;
case QuotingRule.AlwaysQuoted:
scanner.Expect(MatchFunctions.Quoted());
break;
case QuotingRule.NeverQuoted:
scanner.Seek(',', true);
break;
default:
throw new NotImplementedException("Unimplemented quoting rule");
}
parms.Add(scanner.Match);
if (!scanner.Finished)
{
scanner.Next();
index++;
}
}
return parms.ToArray();
}
/// <summary>
/// Parse single argument passed to a pseudoselector
/// </summary>
///
/// <exception cref="ArgumentException">
/// Thrown when one or more arguments have unsupported or illegal values.
/// </exception>
/// <exception cref="NotImplementedException">
/// Thrown when the requested operation is unimplemented.
/// </exception>
///
/// <param name="value">
/// The arguments.
/// </param>
///
/// <returns>
/// The parsed string
/// </returns>
protected string ParseSingleArg(string value)
{
IStringScanner scanner = Scanner.Create(value);
var quoting = ParameterQuoted(0);
switch (quoting)
{
case QuotingRule.OptionallyQuoted:
scanner.Expect(MatchFunctions.OptionallyQuoted());
if (!scanner.Finished)
{
throw new ArgumentException(InvalidArgumentsError());
}
return scanner.Match;
case QuotingRule.AlwaysQuoted:
scanner.Expect(MatchFunctions.Quoted());
if (!scanner.Finished)
{
throw new ArgumentException(InvalidArgumentsError());
}
return scanner.Match;
case QuotingRule.NeverQuoted:
return value;
default:
throw new NotImplementedException("Unimplemented quoting rule");
}
}
/// <summary>
/// Validates a parameter array against the expected number of parameters.
/// </summary>
///
/// <exception cref="ArgumentException">
/// Thrown when the wrong number of parameters is passed.
/// </exception>
///
/// <param name="parameters">
/// Criteria (or parameter) data passed with the pseudoselector.
/// </param>
protected virtual void ValidateParameters(string[] parameters) {
if (parameters == null)
{
if (MinimumParameterCount != 0) {
throw new ArgumentException(ParameterCountMismatchError());
} else {
return;
}
}
if ((parameters.Length < MinimumParameterCount ||
(MaximumParameterCount >= 0 &&
(parameters.Length > MaximumParameterCount))))
{
throw new ArgumentException(ParameterCountMismatchError());
}
}
/// <summary>
/// Gets the string for a parameter count mismatch error.
/// </summary>
///
/// <returns>
/// A string to be used as an exception message.
/// </returns>
protected string ParameterCountMismatchError()
{
if (MinimumParameterCount == MaximumParameterCount )
{
if (MinimumParameterCount == 0)
{
return String.Format("The :{0} pseudoselector cannot have arguments.",
Name);
}
else
{
return String.Format("The :{0} pseudoselector must have exactly {1} arguments.",
Name,
MinimumParameterCount);
}
} else if (MaximumParameterCount >= 0)
{
return String.Format("The :{0} pseudoselector must have between {1} and {2} arguments.",
Name,
MinimumParameterCount,
MaximumParameterCount);
}
else
{
return String.Format("The :{0} pseudoselector must have between {1} and {2} arguments.",
Name,
MinimumParameterCount,
MaximumParameterCount);
}
}
/// <summary>
/// Get a string for an error when there are invalid arguments
/// </summary>
///
/// <returns>
/// A string to be used as an exception message.
/// </returns>
protected string InvalidArgumentsError()
{
return String.Format("The :{0} pseudoselector has some invalid arguments.",
Name);
}
#endregion
</pre>
</div>
</li>
<li>
<a href="#example"><h3>PseudoSelectorChild Abstract Class</h3></a>
<div>
<pre class="prettyprint myprettyprint">
public abstract class PseudoSelectorChild:
PseudoSelector, IPseudoSelectorChild
{
/// <summary>
/// Test whether an element matches this selector.
/// </summary>
///
/// <param name="element">
/// The element to test.
/// </param>
///
/// <returns>
/// true if it matches, false if not.
/// </returns>
public abstract bool Matches(IDomObject element);
/// <summary>
/// Basic implementation of ChildMatches, runs the Matches method
/// against each child. This should be overridden with something
/// more efficient if possible. For example, selectors that inspect
/// the element's index could get their results more easily by
/// picking the correct results from the list of children rather
/// than testing each one.
///
/// Also note that the default iterator for ChildMatches only
/// passed element (e.g. non-text node) children. If you wanted
/// to design a filter that worked on other node types, you should
/// override this to access all children instead of just the elements.
/// </summary>
///
/// <param name="element">
/// The parent element.
/// </param>
///
/// <returns>
/// A sequence of children that match.
/// </returns>
public virtual IEnumerable<IDomObject> ChildMatches(IDomContainer element)
{
return element.ChildElements.Where(item => Matches(item));
}
}
</pre>
</div>
</li>
<li>
<a href="#example"><h3>PseudoSelectorFilter Abstract Class</h3></a>
<div>
<pre class="prettyprint myprettyprint">
public abstract class PseudoSelectorFilter:
PseudoSelector, IPseudoSelectorFilter
{
/// <summary>
/// Test whether an element matches this selector.
/// </summary>
///
/// <param name="element">
/// The element to test.
/// </param>
///
/// <returns>
/// true if it matches, false if not.
/// </returns>
public abstract bool Matches(IDomObject element);
/// <summary>
/// Basic implementation of ChildMatches, runs the Matches method
/// against each child. Same caveats as above.
/// </summary>
///
/// <param name="element">
/// The parent element.
/// </param>
///
/// <returns>
/// A sequence of children that match.
/// </returns>
public virtual IEnumerable<IDomObject> Filter(IEnumerable<IDomObject> elements)
{
return elements.Where(item => Matches(item));
}
}
</pre>
</div>
</li>
</ul>
<p>
If you implement one of the abstract classes, you get most of the functionality pre-rolled:
<ul><li><code>Name</code> is the un-camel-cased name of the class itself, e.g. <code>class MySpecialSelector</code> would become a selector <code>:my-special-selector</code>
<li><code>MinimumParameterCount</code> and <code>MaximumParameterCount</code> are 0, meaning no parenthesized parameters.
<li><code>Arguments</code> is parsed into a protected property <code>string Parameters[]</code> (using comma as a separator) using the min/max values as a guide. Additionally, you can override <code>QuotingRule ParameterQuoted(int index)
</code> and return a value to tell the class how to parse each parameter. The <code>index</code> refers to the zero-based position of the parameter, and <code>QuotingRule</code> is an enum that indicates how quoting should be handled for the parameter at that position: NeverQuoted, AlwaysQuoted or OptionallyQuoted. NeverQuoted means single and double quotes will be treated as regular characters, and AlwaysQuoted means single or double-quote bounds are required. OptionallyQuoted means that if found, they will be treated as bounding quotes, but are not required.
<li>The <code>PseudoSelectorChild</code> class implements <code>ChildMatches</code> by simply passing each <i>element</i> child to the <code>Matches</code> function. If you want to test other types of children (like text nodes) or have a smarter way to choose matching children, then override it.
</ul>
</p>
<h3>Adding Your Selector to CsQuery</h3>
<p>
Here's the cool part. To add your selector to CsQuery, <i>you don't need to do anything.</i>. If you include it in a namespace called <code>CsQuery.Extensions</code>, it will automatically be detected. This works as long as this extension can be found in the assembly which first invokes a selector when the application starts. If for some reason this might not be the case, you can force CsQuery to register the extensions explicitly by calling from the assembly in which they're found:
<pre class="prettyprint myprettyprint">
CsQuery.Config.PseudoClassFilters.Register();
</pre>
You can also pass an <code>Assembly</code> object to that method. Finally, you can register a filter type explicitly:
<pre class="myprettyprint">
CsQuery.Config.PseudoClassFilters.Register("my-special-selector",typeof(MySpecialSelector));
</pre>
The <code>Name</code> property isn't used when you register an extension this way.
</p>
<h3>Example</h3>
<p>
Here's an port of the <code>:regex</code> selector mentioned above. This can also be found in the test suite under <code>CSharp\Selectors\RegexExtension.cs</code>.
</p>
<ul id="accordion" class="accordion">
<li class="inactive">
<a href="#example"><h3>Regular Expression Filter Code</h3></a>
<div>
<pre class="prettyprint myprettyprint">
using System.Text.RegularExpressions;
using CsQuery.ExtensionMethods;
class Regex : PseudoSelectorFilter
{
private enum Modes
{
Data = 1,
Css = 2,
Attr = 3
}
private string Property;
private Modes Mode;
private SysRegex Expression;
public override bool Matches(IDomObject element)
{
switch (Mode)
{
case Modes.Attr:
return Expression.IsMatch(element[Property] ?? "");
case Modes.Css:
return Expression.IsMatch(element.Style[Property] ?? "");
case Modes.Data:
return Expression.IsMatch(element.Cq().DataRaw(Property) ?? "");
default:
throw new NotImplementedException();
}
}
private void Configure()
{
var validLabels = new SysRegex("^(data|css):");
if (validLabels.IsMatch(Parameters[0]))
{
string[] subParm = Parameters[0].Split(':');
string methodName = subParm[0];
if (methodName == "data")
{
Mode = Modes.Data;
}
else if (methodName == "css")
{
Mode = Modes.Css;
}
else
{
throw new ArgumentException("Unknown mode for regex pseudoselector.");
}
Property = subParm[1];
}
else
{
Mode = Modes.Attr;
Property = Parameters[0];
}
// The expression trims whitespace the same way as the original
// Trim() would work just as well but left this way to demonstrate
// the CsQuery "RegexReplace" extension method
Expression = new SysRegex(Parameters[1].RegexReplace(@"^\s+|\s+$",""),
RegexOptions.IgnoreCase | RegexOptions.Multiline);
}
// We override "Arguments" to do some setup when this selector
// is first created, rather than parse the arguments on each
// iteration as in the Javascript version. This technique should
// be used universally to do any argument setup. Selectors with no
// arguments by definition should have no instance-specific
// configuration to do, so there would be no point in overriding
// this for that kind of filter.
public override string Arguments
{
get
{
return base.Arguments;
}
set
{
base.Arguments = value;
Configure();
}
}
// Allow either parameter to be optionally quoted since they're both
// strings: just return null regardless of index.
protected override bool? ParameterQuoted(int index)
{
return null;
}
public override int MaximumParameterCount
{
get { return 2; }
}
public override int MinimumParameterCount
{
get { return 2; }
}
public override string Name
{
get { return "regex"; }
}
}
</pre>
</div>
</li></ul>
<p>
This is actually a relatively complicated pseduo-selector. To see some simpler examples, just go look at the <a href="https://github.com/jamietre/CsQuery/tree/master/source/CsQuery/Engine/PseudoClassSelectors">source code</a> for the CsQuery CSS selector engine. Most of the native selectors have been implemented using this API. The exceptions are pseudoselectors that match only on indexed characteristics, e.g. all the tag and type selectors such as <code>:input</code> and <code>:checkbox</code>. These could have been set up the same way, but they wouldn't be able to take advantage of the index if they were implemented as filters.
</p>
<h3>Speaking Of Which... Selector Performance</h3>
<p>Many of the same rules about selector performance apply here as they do in jQuery. Don't do this:
<pre class="myprettyprint prettyprint">
var sel = doc[":some-filter"].Filter("div");
</pre>
<p>Do this:</p>
<pre class="myprettyprint prettyprint">
var sel = doc["div:some-filter"];
</pre>
<p>
Obviously that's a pretty silly example - most people wouldn't go out of their way to do the first. But generally speaking, you should order your selectors this way:
</p>
<ul><li>ID, tag, and class selectors first;
<li>attribute selectors next;
<li>filters last
</ul>
<p>
Unlike jQuery, it doesn't matter whether a filter or selector is "native" to CSS or not - everything is native in CsQuery. What matters is whether it's indexed. All <b>attribute names</b> (but not values), <b>node names</b> (tags), <b>classes</b> and <b>ID values</b> are indexed. It doesn't matter if you combine selectors -- the index can still be used as long as you're selecting on one of those things. But you should try to organize your selectors to chose the <i>most specific indexed criteria first.</i>
</p>
<op>
It's very fast for CsQuery to pull records from the index. So if you are targeting an ID, that's unique - always use that first. Classes are probably the next best, followed by tag names, and last attributes. Nodes with a certain attribute will be identified in the index just as fast as anything else, but then the engine still has to check the value of each node that has that attribute against your selection criteria.
</p>
<hr />
<p>
<i>CsQuery is a complete CSS selector engine and jQuery port for .NET4 and C#. It's on NuGet as <a href="http://www.nuget.org/packages/CsQuery">CsQuery</a>. For documentation and more information please see the <a href="http://www.github.com/jamietre/csquery">GitHub repository</a> and <a href="http://blog.outsharked.com/search/label/csquery">posts about CsQuery</a> on this blog.
</i></p>Jamiehttp://www.blogger.com/profile/04089108417465569092noreply@blogger.com0tag:blogger.com,1999:blog-2883880512830686149.post-18540455107476730002012-06-27T15:27:00.000-04:002012-06-28T15:33:22.205-04:00CsQuery Performance vs. Html Agility Pack and Fizzler<p>I put together some performance tests to compare CsQuery to the only practical alternative that I know of (Fizzler, an HtmlAgilityPack extension). I tested against three different documents:
</p>
<ul><li>The sizzle test document (about 11 k)</li>
<li>The wikipedia entry for "cheese" (about 170 k)</li>
<li>The single-page HTML 5 spec (about 6 megabytes)</li>
</ul>
<p>The overall results are:</p>
<ul><li><b>HAP is faster at loading the string of HTML into an object model.</b> This makes sense, since I don't think Fizzler builds an index (or perhaps it builds only a relatively simple one). CsQuery takes anywhere from 1.1 to 2.6x longer to load the document. More on this below.</li>
<li><b>CsQuery is faster for almost everything else.</b> Sometimes by factors of 10,000 or more. The one exception is the "*" selector, where sometimes Fizzler is faster. For all tests, the results are completely enumerated; this case just results in every node in the tree being enumerated. So this doesn't test the selection engine so much as the data structure.</li>
<li><b>CsQuery did a better job at returning the same results as a browser.</b> Each of the selectors here was verified against the same document in Chrome using jQuery 1.7.2, and the numbers match those returned by CsQuery. This is probably because HtmlAgilityPack handles optional (missing) tags differently. Additionally, <code>nth-child</code> is not implemented completely in Fizzler - it only supports simple values (not formulae).
</li></ul>
<p>
The most dramatic results are when running a selector of a single ID or a nonexistent ID in a large document. CsQuery returns the result (an empty set) over 100,000 times faster than Fizzler. This is almost certainly because it doesn't index on IDs; other selectors are much faster in Fizzler than this (though still substantially slower than CsQuery).
</p>
<h3>Size Matters</h3>
<p>
In the very small documents (the 11k sizzle test document) CsQuery still beats Fizzler, but by much less. The ID selector is still pretty substantial about 15-15x faster. For more complex selectors, the margin is just over 1x to 3x faster.
</p>
<p>On the other hand, in very large documents, the edge that Fizzler has in loading the documents seems to mostly disappear. CsQuery is only about 10% slower at loading the 6 megabyte "large" document. This could be an opportunity for optimizing CsQuery - this seems to indicate that overhead just in creating a single document is dragging performance down. Or, it could be indicative of the makeup of the respective test documents. Maybe CsQuery does better with more elements, and Fizzler with more text - or vice versa.
</p>
<p>
You can see a detailed comparison of all the tests so far <a href="https://docs.google.com/spreadsheet/lv?key=0AiIxtWMJ80OndHNVSnpwZ2toTjBRNk1KUDRIbWdTRWc&pli=1&rm=full#gid=0">here</a> in a google doc:</p>
<iframe style="overflow: scroll; width:100%; height: 300px;" src="https://docs.google.com/spreadsheet/lv?key=0AiIxtWMJ80OndHNVSnpwZ2toTjBRNk1KUDRIbWdTRWc&pli=1&rm=full#gid=0"></iframe>
<h2>"FasterRatio" is how much faster the winner was than the loser. Yellow ones are CsQuery; red ones are Fizzler.</h2>
<h2>Red in the "Same" column means the two engines returned different results.</h2>
<h3>Try It Out</h3>
<p>
This output can be created directly from the CsQuery test project under "Performance."
</p>
<hr />
<p>
<i>CsQuery is a complete CSS selector engine and jQuery port for .NET4 and C#. It's on NuGet as <a href="http://www.nuget.org/packages/CsQuery">CsQuery</a>. For documentation and more information please see the <a href="http://www.github.com/jamietre/csquery">GitHub repository</a> and <a href="http://blog.outsharked.com/search/label/csquery">posts about CsQuery</a> on this blog.
</i></p>Jamiehttp://www.blogger.com/profile/04089108417465569092noreply@blogger.com3tag:blogger.com,1999:blog-2883880512830686149.post-59194190428976116912012-06-26T16:04:00.000-04:002012-07-20T10:02:33.405-04:00CsQuery 1.1.2 Released<p><b>CsQuery 1.1.2 has been released.</b> You can get it from <a href="http://www.nuget.org/packages/CsQuery">NuGet</a> or from the source repository on <a href="http://www.github.com/jamietre/csquery">GitHub</a>.
</p>
<h2>New features</h2>
<p>
This release includes significant revisions to the HTML parser to enhance compatibility with HTML5 <a target="_blank" href="http://www.whatwg.org/specs/web-apps/current-work/multipage/syntax.html#optional-tags">parsing rules for optional opening and closing tags</a>.
</p><p>When optional closing tags are omitted, such as <code></p></code>, CsQuery's HTML parser will use the HTML5 spec rules to determine when to insert a closing tag. When opening tags for required elements such as <code>head</code> and <code>tbody</code> are omitted, the parser will <i>generate the missing tags</i> when parsing in document mode. This means you can expect a very high degree of compatibility between the HTML (and selections) generated by CsQuery, and the DOM rendered by web browsers, when valid HTML is passed.
<p/>
<p>The HTML5 spec also includes a set of rules for handling invalid markup. While the CsQuery parser usually makes pretty good decisions about how to handle bad HTML, and should be able to parse about anything, it doesn't yet comply with the "bad markup" part of the spec - just the "optional" handling part. Over time, though, I intend to continue improving the parser to comply with other parts of the spec as much as possible.
</p>
<h2>API Change</h2>
<p>
Because the HTML parser will generate tags now, it needs to understand context. If you're creating a fragment that's just supposed to be a building block, you obviously don't want it adding <code>html</code> and <code>body</code> tags around your markup.
</p>
<p>
There are now three static methods for parsing HTML:
<ul><li><code>CQ.Create(..)</code><br /><i>
Create a content block</i><br /><br />
This method is meant to be used for complete HTML blocks that are not self-contained documents. Examples of this are a piece of content retrieved from a CMS, or a template. It should be used for anything that is a compete block, but is intended to be embedded in another document. Using this method, missing tags will be handled according to the HTML5 spec EXCEPT for adding the optional <code>html</code> and <code>body</code> tags. Additionally, any text found at the root of the markup will be wrapped in <code>span</code> tags making it safe to insert into nodes that cannot have text directly as children.<br /><br />
<li><code>CQ.CreateDocument(..)</code><br /><i>
Create a document.</i><br /><br />
This method creates a complete HTML document. If the <code>html</code>, <code>body</code> or <code>head</code> tags are missing, they will be created. Stranded text nodes (e.g. outside of <code>body</code>) will be moved inside the body. If you're parsing HTML from the web or from a file that's supposed to represent a complete HTML document, use this.<br /><br />
<li><code>CQ.CreateFragment(..)</code><br /><i>
Create a fragment.</i>
<br /><br />
This method interprets the content as a true fragment that you can use for any purpose. No new elements will be created. The rules for optional closing tags are still honored -- to do otherwise would just result in the default handling for any broken/unclosed tag being used instead. But no optional tags like <code>tbody</code> will be generated even if they are expected to be found. This method is the default handling for creating HTML from a selector, e.g. <pre class="myprettyprint">var html = dom["<div></div>"];</pre>
</ul>
</p>
<p></p>
<h2>Other Enhancements</h2>
<ul><li>The jQuery <code>:input</code> pseudoclass was added. It had been inadvertently omitted from prior versions.
<li>All selectors can include escaped characters now
<li>HTML parser permits all valid characters in class and attribute names. Previously, the : and . characters were stop characters.
<li>The <code>CQ</code> object's property indexer overloads now align with the <code>Select</code> method overloads.
<li>Migrated all of the tests from Sizzle. (A few of the bugs fixed in this release were found as a result of implementing the Sizzle test suite).
</ul>
<h2>Bug Fixes</h2>
<ul><li><a href="https://github.com/jamietre/CsQuery/issues/12">Issue #12</a>: CSS class names being output in lowercase
<li><a href="https://github.com/jamietre/CsQuery/issues/11">Issue #11</a>: <code>:hidden</code> selector not selecting <code>input[type=hidden]</code>
<li><a href="https://github.com/jamietre/CsQuery/issues/8">Issue #8</a>: allow leading + and - signs in nth-child type equations
<li>Corrected a problem with some last-child selectors (found during <a target="_blank" href="https://github.com/jquery/sizzle/tree/master/test/unit">Sizzle unit test</a> migration, no bug report)
</ul>
<p>
This release has also had some performance optimizations; nth-child type selectors in particular should be an order of magnitude faster as a result of caching the results of each calculation.
</p>
<hr />
<p>
<i>CsQuery is a complete CSS selector engine and jQuery port for .NET4 and C#. It's on NuGet as <a href="http://www.nuget.org/packages/CsQuery">CsQuery</a>. For documentation and more information please see the <a href="http://www.github.com/jamietre/csquery">GitHub repository</a> and <a href="http://blog.outsharked.com/search/label/csquery">posts about CsQuery</a> on this blog.
</i></p>Jamiehttp://www.blogger.com/profile/04089108417465569092noreply@blogger.com0tag:blogger.com,1999:blog-2883880512830686149.post-69960022181701429772012-06-19T08:23:00.000-04:002012-06-25T08:41:18.487-04:00ImageMapster 1.2.5 released<style type="text/css">
ul li {
padding: 8px;
}
</style>
<p>After 9 months I've finally released an update to ImageMapster. <a href="https://github.com/downloads/jamietre/ImageMapster/jquery.imagemapster.zip">Download the latest release distribution</a> or <a href="https://github.com/jamietre/ImageMapster">go to github</a> to see the source.
<p>Since 1.2.4 much has changed. If you've been following along the development, a lot of this may be old news, but this covers most of what's changed since the last official release.
<h3>New Features</h3>
<ul>
<li><a target="_blank" href="http://www.outsharked.com/imagemapster/default.aspx?docs.html#clickNavigate"><code>clickNavigate</code></a> allows binding a URL to an area, <i>just like a regular HTML imagemap!</i> Seriously, this was a common request - sometimes people just wanted the map to highlight areas on mouseover, but otherwise act the same. You could always do this by capturing a click event and then set <code>window.location</code>, but the method streamlines this. <br /><br />It offers a few conveniences, e.g. when an area has only <code>href='#'</code> then it will not navigate even when this option is enabled, and if any valid href target is found on any area in a group, then it will be used no matter which area in the group is clicked.<br /><br /></li>
<li>A new <a target="_blank" href="http://www.outsharked.com/imagemapster/default.aspx?docs.html#keys">keys</code></a> option allows you to obtain a list of keys associated with an area or area group. That is, you can assign more than one key to an area, e.g. this area:
<pre class="prettyprint"><area href="#" data-key="area1, group1" coords="..."></pre>
has two keys, "area1" and "group1." This lets you create different, independent groups which you can control separately. The first key on the list is always the primary key, though, and determines whether something is considered "selected". So sometimes, given a key, you want to find out other keys associated with it, so you can select or deselect associated areas in response to an action. This option gives you easy access to data on the relationships between area keys.<br /><br />
</li>
<li><a target="_blank" href="http://www.outsharked.com/imagemapster/default.aspx?docs.html#mouseoutDelay"><code>mouseoutDelay</code></a> option lets you specify a time in milliseconds that a highlighted area will remain highlighted after the mouse leaves. (If another area is highlighted before this time elapses, the old one will be removed immediately). This is useful for <i>sparse</i> maps, e.g. maps with large areas that aren't part of the map and only small highlighted areas. Because a users's pointer may only be over the area briefly, the effect could appear flickery or jerky. This allows you to keep it highlighted for some time after they leave to avoid this problem.<br /><br />
<li>Rendering options can be passed on-the-fly with <a target="_blank" href="http://www.outsharked.com/imagemapster/default.aspx?docs.html#set"><code>set</code></a> allowing you to have complete control over the appearance of every area without having to define area options up front.
</ul>
<br />
<h3>Bug Fixes, Improvements</h3>
<ul>
<li>Many compatibility and stability improvements to resolve conflicts with browser plugins (AdBlock in particular) and solve some browser issues. Fading effects now work consistently in IE 6-8 too.</li>
<li>More robust binding to handle situations that caused problems such as the imagemap being initially hidden or extremely slow-loading images</li>
<li>Tooltips can be positioned outside the boundaries of the image. A few bugs related to tooltip positioning were fixed.</li>
<li><code>rebind</code> and <code>snapshot</code> have been cleaned up a lot, allowing you to chain events to create complex initial effects. For example, this code would bind a map using a set of options defined in <code>initial_opts</code>, then highlight "CA" using the "fill" and "fillColor" options shown, then finally take a snapshot and rebind with a different set of options <code>basic_opts</code>. All the effects that were rendered before the <code>snapshot</code> will now be part of a static backdrop. <a target="_blank" href="http://jsfiddle.net/jamietre/Wvzgj/">Fiddle with it.</a>
<pre class="myprettyprint prettyprint">
$('img').mapster(initial_opts)
.mapster('set',true,'CA', {
fill: true,
fillColor: '00ff00'
})
.mapster('snapshot')
.mapster('rebind',basic_opts);
</pre></li>
<li><a target="_blank" href="http://www.outsharked.com/imagemapster/default.aspx?docs.html#resize"><code>resize</code></a> has been improved to increase smoothness and performance. A bug that caused its callback to be fired at the wrong time has been fixed.</li>
</ul>
<br />
<h3>What's next?</h3>
<p>
First, I'm not going to wait 9 months to make a new release next time. This was a result of being dissatisfied with the state of javascript testing frameworks for testing complex UI tools. I never felt comfortable calling this a "release" while the tests were a mess. That was probably a mistake since thousands of people downloaded the old version even as I've known it's got many bugs that have since been fixed. I won't make that mistake again.
<p>
The next major release will include a new API as an option. That is, instead of calling mapster with <code>mapster('method',...)</code> you will be able to obtain an actual <code>mapster</code> object and call its methods directly, e.g.
<pre class="myprettyprint prettyprint">
var mapster = $('img').mapster(initial_opts);
mapster.set('CA', {fill: true, fillColor: '00ff00' })
.snapshot()
.rebind(basic_opts);
</pre>
While sticking to the jQuery model makes sense to a point, this tool has become sufficiently built-out that it's a hinderance when doing anything beyond the basics. The old methods will still be perfectly valid.
<p>
<b>There will be panning and zooming.</b> I started coding some more sophisticated zoom effects that work with "resize" to let you easily zoom directly to an area. I stopped when I realized feature creep was preventing me from getting a new release finished and fixing bugs. Now it's time to get back to that.
<p>
<b>Better tooltips.</b> Lots of people ask about controlling the position and functionality of tooltips. I plan to add some better integrated support for tooltip manipulation.
<p>
<b>Feature selection</b> I broke the source code into modules some time ago because it was becoming unwieldy as a single file. My secondary goal in doing this was to allow one to create custom builds using only the features needed. For example, if you don't care about tooltips, why include that extra code? This is really more of a web site feature than anything else, it is (almost) possible to exclude some modules now.
<p><b>What else?</b> Let me know if you have ideas, or want to contribute!Jamiehttp://www.blogger.com/profile/04089108417465569092noreply@blogger.com4tag:blogger.com,1999:blog-2883880512830686149.post-81921319212475368182012-06-13T10:05:00.001-04:002012-07-20T10:02:45.449-04:00CsQuery 1.1 Released, and available on NuGet<p><a href="http://www.github.com/jamietre/csquery">CsQuery 1.1</a> has been released. This is a major milestone; the library now implements every CSS2 and CSS3 selector.</p>
<p>
Additionally, CsQuery is now available on NuGet:
</p>
<pre><code> PM> Install-Package CsQuery</code></pre>
<p>There are two important API changes from prior versions.
<ul>
<li>The <code>IDomElement.NodeName</code> method now returns its results in uppercase. Formerly, results were returned in lowercase. So any code that tests for node type with a string will break, e.g.
<br />
<pre class="prettyprint myprettyprint">
CQ results = dom["div, span"];
foreach (IDomObject item in results) {
// if (item.NodeName=="div") {
if (item.NodeName=="DIV") {
...
}
}
</pre>
<br />
I realize this can easily break code in ways that the compiler cannot detect and apologize for this; but this is important to be consistent with the browser DOM. This was a long time coming.<br /><br /></li>
<li>The <code>CsQuery.Server</code> object has been removed. Methods for loading a DOM from an http server have been replaced with static methods on the <code>CQ</code> object:
<pre class="prettyprint myprettyprint">
// synchronous
var doc = CQ.CreateFromUrl("http://www.jquery.com");
// asynchronous with delegates to call upon completion
CQ.CreateFromUrlAsync("http://www.jquery.com", responseSuccess => {
Dom = response.Dom;
}, responseFail => {
..
});
// asynchronous using IPromise (similar to C#5 Task)
var promise = CQ.CreateFromUrlAsync("http://www.jquery.com");
var promise2 = CQ.CreateFromUrlAsync("http://www.cnn.com");
promise.Then(successDelegate);
promise2.Then(successDelegate,failDelegate);
When.All(promise,promise2).Then(allFinishedDelegate);
</pre>
See <a href="https://github.com/jamietre/csquery#creating-a-new-dom">Creating a new DOM</a> and <a href="https://github.com/jamietre/csquery#promises">Promises</a> in the <a href="https://github.com/jamietre/csquery#readme">readme</a> for more details.</li>
</ul>
<br />
<h3>New Features in 1.1</h3>
<p>
Implemented all missing CSS pseudoclass selectors:
<pre class="prettyprint">
:nth-last-of-type(N) :nth-last-child(N)
:nth-of-type(N) :only-child
:only-of-type :empty
:last-of-type :first-of-type
</pre>
Implemented all missing jquery pseudoclass selectors:
<pre class="prettyprint">
:parent :hidden
:header
</pre>
<ul><li>Added <code>IDomObject.Name</code> property</li>
<li>Added <code>IDomObject.Type</code> property</li></ul>
<h3>Bug Fixes</h3>
<p>
<ul><li>Don't consider <code>html</code> node a child when targeted by child-targeting selectors (consistent with browser behavior)</li>
<li>Fix checkbox lists in <code>Forms.RestorePost</code></li>
<li>Pseudoselectors from a descendant combinator only returning direct descendant matches (e.g., <code>div :empty</code>)</li>
<li><a href="https://github.com/jamietre/CsQuery/issues/5">Issue #5</a> - Remove enforcement of unique id attribute when parsing HTML</li>
</ul>
<hr />
<i>CsQuery is a complete port of jQuery written in C# for .NET4. For documentation and more information please see the <a href="http://www.github.com/jamietre/csquery">GitHub repository</a> and <a href="http://blog.outsharked.com/search/label/csquery">posts about CsQuery</a> on this blog.
</i></p>Jamiehttp://www.blogger.com/profile/04089108417465569092noreply@blogger.com0tag:blogger.com,1999:blog-2883880512830686149.post-14168817869918858802012-06-07T08:03:00.000-04:002012-06-07T13:01:42.112-04:00Async web gets and Promises in CsQuery<p>More recent versions jQuery introduced a "deferred" object for managing callbacks using a concept called <a href="http://wiki.commonjs.org/wiki/Promises/A">Promises.</a> Though this is less relevant for <a href="http://www.github.com/jamietre/csquery">CsQuery</a> because your work won't be interactive for the most part, there is one important situation where you will have to manage asynchronous events: loading data from a web server.
</p><p>
Making a request to a web server can take a substantial amount of time, and if you are using CsQuery for a real-time application, you probably won't want to make your users wait for the request to finish.
</p><p>
For example, I use CsQuery to provide current status information on the "What's New" section for the <a href="http://www.outsharked.com/imagemapster">ImageMapster</a> web site. I do this by scraping GitHub and parsing out the relevant information. But I certainly do not want to cause anyone to wait while the server makes a remote web request to GitHub (which could be slow or inaccessible). Rather, the code keeps track of when the last time it's updated it's information using a static variable. If it's become "stale", it initiates a new async request, and when that request is completed, it updates the cached data.
</p><p>
So, the http request that actually triggered the update will be shown the old information, but there will be no lag. Any requests coming in after the request to GitHub has finished will of course use the new information. The code looks pretty much like this:
</p>
<pre class="prettyprint myprettyprint">
private static DateTime LastUpdate;
if (LastUpdate.AddHours(4) < DateTime.Now) {
/// stale - start the update process. The actual code makes three
/// independent requests to obtain commit & version info
var url = "https://github.com/jamietre/ImageMapster/commits/master";
CQ.CreateFromUrlAsync(url)
.Then(response => {
LastUpdate = DateTime.Now;
var gitHubDOM = response.Dom;
...
// use CsQuery to extract needed info from the response
});
}
...
// render the page using the current data - code flow is never blocked even if an update
// was requested
</pre>
<p>
Though C# 5 includes some language features that greatly improve asynchronous handling such as `await`, I dind't want to "wait", and the promise API used often in Javascript is actually extraordinarily elegant. Hence I decided to make a basic C# implementation to assist in using this method.
</p><p>
The `CreateFromUrlAsync` method can return an `IPromise<ICsqWebResponse>` object. The basic promise interface (from CommonJS Promises/A) has only one method:
</p>
<pre class="prettyprint">
then(success,failure,progress)
</pre>
<p>
The basic use in JS is this:
</p>
<pre class="prettyprint">
someAsyncAction().then(successDelegate,failureDelegate);
</pre>
<p>
When the action is completed, "success" is called with an optional parameter from the caller; if it fails, "failure" is called.
</p><p>
I decided to skip progress for now; handling the two callbacks in C# requires a bit of overloading because function delegates can have different signatures. The CsQuery implementation can accept any delegate that has zero or one parameters, and returns void or something. A promise can also be generically typed, with the generic type identifying the type of parameter that is passed to the callback functions. So the signature for `CreateFromUrlAsync` is this:
</p>
<pre class="prettyprint">
IPromise<ICsqWebResponse> CreateFromUrlAsync(string url, ServerConfig options = null)
</pre>
<p>
This makes it incredibly simple to write code with success & failure handlers inline. By strongly typing the returned promise, you don't have to cast the delegates, as in the original example: the `response` parameter is implicitly typed as `ICsqWebResponse`. If I wanted to add a fail handler, I could do this:
</p>
<pre class="prettyprint myprettyprint">
CQ.CreateFromUrlAsync(url)
.Then(responseSuccess => {
LastUpdate = DateTime.Now;
...
}, responseFail => {
// do something
});
</pre>
<p>
CsQuery provides one other useful promise-related function called `WhenAll`. This lets you create a new promise that resolves when every one of a set of promises has resolved. This is especially useful for this situation, since it means you can intiate several independent web requests, and have a promise that resolves only when all of them are complete. It works like this:
</p>
<pre class="prettyprint myprettyprint">
var promise1 = CQ.CreateFromUrlAsync(url);
var promise2 = CQ.CreateFromUrlAsync(url);
CsQuery.When.All(promise1,promise2).Then(successDelegate, failDelegate);
</pre>
<p>
You can also give it a timeout which will cause the promise to reject if it has not resolved by that time. This is valuable for ensuring that you get a resolution no matter what happens in the client promises:
</p>
<pre class="prettyprint myprettyprint">
// Automatically reject after 5 seconds
CsQuery.When.All(5000,promise1,promise2)
.Then(successDelegate, failDelegate);
</pre>
<p>
`When` is a static object that is used to create instances of promise-related functions. You can also use it to create your own deferred entities:
</p>
<pre class="prettyprint myprettyprint">
var deferred = CsQuery.When.Deferred();
// a "deferred" object implements IPromise, and also has methods to resolve or reject
deferred.Then(successDelegate, failDelegate);
deferred.Resolve(); // causes successDelegate to run
</pre>
<p>
What's interesting about promises, too, is that they can be resolved *before* the appropriate delegates have been bound and everything still works:
</p>
<pre class="prettyprint myprettyprint">
var deferred = CsQuery.When.Deferred();
deferred.Resolve();
deferred.Then(successDelegate, failDelegate); // successDelegate runs immediately
</pre>
<p>
I may completely revisit this once VS2012 is out; the `await` keyword cleans things up a little but and the `Task.WhenAll` feature does the same thing as `When.All` here.
By the way - the basic API and operation for "when" was 100% inspired by Brian Cavalier's excellent <a href="https://github.com/cujojs/when">when.js</a> project which I use extensively in Javascript.
</p>Jamiehttp://www.blogger.com/profile/04089108417465569092noreply@blogger.com0tag:blogger.com,1999:blog-2883880512830686149.post-78267006081211216852012-06-04T10:45:00.001-04:002012-07-17T12:39:14.499-04:00Using CsQuery with MVC views<p><b>Update 7/17/2012:</b>
The source repository now includes a complete MVC example project that implements a custom view engine using CsQuery, allowing you to simply add methods to a controller to have access to the page's markup before rendering as a CQ object, e.g.
<pre class="prettyprint myprettyprint">
public class AboutController : CsQueryController
{
public ActionResult Index()
{
return View();
}
// runs for the "Index" action after the ActionResult is returned,
// providing access to the final HTML before it's rendered
public void Cq_Index()
{
// add the "highlight" class to all anchors
Doc["a"].AddClass("highlight");
}
}
</pre>
Take a look at the <a href="https://github.com/jamietre/CsQuery/tree/master/examples/CsQuery.MvcApp">MVC example</a> for more information. The contents of this blog post are accurate but the example provides much more detail as well as a complete implementation, since it's not completely trivial to intercept the final HTML for a page in an MVC application.
<p>
<p><b>Original Post</b></p>
<p>I've been neglecting <a href="https://github.com/jamietre/csquery">CsQuery</a>, the C# jQuery port lately, and I feel bad about that. But I haven't forgotten it. Quite the opposite, I'm gearing up to create the first formal release, get it onto NuGet, and publish a web site with interactive demos and documentation. It's going to take a little while to move this all forward but it's in progress.</p>
<p>There's been a spark of outside interest in the project in the last month or so, which has inspired me to get moving again on some of this stuff. Things always slow down at work in the summer so the timing is good and I hope to have this thing in a more consumer-friendly format soon.</p>
<p>In the meantime, <a href="http://www.west-wind.com/weblog/posts/2012/May/30/Rendering-ASPNET-MVC-Views-to-String?utm_source=feedburner&utm_medium=feed&utm_campaign=Feed%3A+RickStrahl+%28Rick+Strahl%27s+WebLog%29">here's a nugget</a> from Rick Strahl about rendering MVC views as strings. If you're using CsQuery with ASP.NET MVC, this is a technique you will almost certainly use to feed your MVC markup into CsQuery for further manhandling.
</p>
<p>
I described a similar technique in <a href="https://github.com/jamietre/CsQuery/issues/2">this question</a>. Rick's post encapsulates this cleanly in a class. To get from there to a CsQuery object is a piece of cake:
</p>
<pre class="prettyprint myprettyprint">
string message=ViewRenderer.RenderView("~/views/template/ContactSellerEmail.cshtml",
model,ControllerContext);
// create a CsQuery object from the HTML string
CQ messageDom = CQ.Create(message);
// do stuff...
messageDom["#content-placeholder"].ReplaceWith(...);
// render it back to a string of HTML
message = messageDom.Render();
</pre>Jamiehttp://www.blogger.com/profile/04089108417465569092noreply@blogger.com0tag:blogger.com,1999:blog-2883880512830686149.post-25631082511715840242012-05-15T04:48:00.001-04:002012-05-17T10:05:19.647-04:00On Javascript Style<p>My two cents on the ongoing debate over punctuation in Javascript.
</p><p>This <a href="https://gist.github.com/357981">two year old gist</a> from Isaac Schlueter, author of NPM, has been a focal point for this debate. He describes a rationale for putting commas first, beginning with these simple words:
<blockquote><b>Note how the errors pop out in the comma-first style.</b></blockquote>
</p><p>He's absolutely right. And that, right there, is why I think it's a bad style choice.
</p><p><i>I want punctuation to disappear when it's not doing something important.</i> Commas that separate array elements or object properties that are <i>already on separate lines</i> are not doing anything important. So putting commas first may save me from a few comma-related errors every day. (Which, of course, JSHINT will catch promptly, along with all the other errors I make every day). But I will spend ten or twelve hours a day, every day, looking at commas instead of variable names or object definitions. I will have to visually parse the punctuation for every single line I read, instead of not thinking about it.
</p><p>I agree that putting commas first makes errors more apparent. I disagree that an entire aesthetic should be defined by this desire.
</p><p>There are some arguments made over the course of this two-year-old (and still going strong) gist about the sorts of errors that automated syntax checking tools will not catch, also justifying this. For example, this is valid:
<pre class="myprettyprint">
var x = [ ["asdf", "foo", "bar"],
["baz", "blerg", "boof"]
["quux", "antimatter"] ];
</pre>
</p><p>The parser will interpret the last element as a property indexer, which is wrong.
</p><p>All I can say is, <i>how much time do you spend hardcoding two-dimensional arrays?</i> On the other hand, how much time do you spend reading object constructs and var statements? The latter are the foundation for pretty much every bit of code you will write in Javascript. The former is an anomaly.
</p><p>Yes. You can come up with of situations where the error introduced by a missing comma would resemble valid code and not be caught by JSHINT. A comma-first syntax would probably have helped you avoid that problem.
</p><p>I am not sure such a situation has ever come up in practice, though. This is OCD. This is carrying a bottle of antiseptic and spraying every surface before you touch it, because it (maybe) will prevent you from getting a cold once in a while. <i>The cure is much worse than the disease!</i>
</p><p>Avoiding syntax errors is important. But is it more important than making code that can be read and understood with the least amount of effort? I don't think so. If comma-first adds even 1% to the amount of mental energy I need to read and understand some code, it's not worth it.
</p>
<p>
One final note. I'm not a purist at all. Use multiple var statements (but only before your first line of functional code, of course!) Use side effects. Don't use semicolons. I think the goal should be to write code that is the most easily understood, and a lot of "non-purist" techniques can be used to create terse code that's more readable than its longform version. I feel like a lot of the rhetoric on this topic has been about a debate over purism. It shouldn't be.</p>
<p>I have a tendency to use semicolons rigorously, but that's because I program in C# half the time and it's hard to get my brain to move easily back and forth between the two mindsets.
But I completely understand the rationale often cited for using ASI: <i>semicolons are visual clutter.</i> Yup, they sure are! And I think that same rationale should apply doubly so for putting commas at the beginning of the line instead of the end. At least when punctuation is at the end of a line, our "left to right" brains can easily dismiss it. When it's at the beginning, you can't avoid it.
</p>Jamiehttp://www.blogger.com/profile/04089108417465569092noreply@blogger.com0tag:blogger.com,1999:blog-2883880512830686149.post-9290725847137000182012-03-29T15:33:00.000-04:002012-03-29T15:42:12.422-04:00Passing control from a custom HttpHandler to the default handler in asp.net<p>Using <code>System.Web.Routing</code> and IIS7 you can do some pretty interesting stuff with a web app. It also opens up a lot of possibilities for "modernizing" old webforms applications that are stuck with ugly paths and query strings by overriding the default handlers and parsing out the page path yourself.
</p><p>
One thing I wanted to do was to map certain paths back to an aspx page, but dynamically - e.g. I wanted to be able to parse out a path and using complex logic, build a new "real" path+querystring that I could just pass to the default handler. This way I can create nice clean API-like paths and map them to an old, ugly query-string based API. The routing part is a piece of cake. (Well, not really, but it's not a mystery). But how do you invoke the default handler from code? There's a baffling lack of info out there, so I figured I'd post a solution for future coders.
</p><p>
After some digging I realized that, actually, the default handler is just the plain old <code>System.Web.UI.Page</code> object. I tried to just create an instance of one and call <code>ProcessRequest</code> like you would any other handler. Nothing at all. No error, no output.
</p><p>
Microsoft is decidedly no help with this either. For <code>Page.ProcessRequest</code>, <a href="http://msdn.microsoft.com/en-us/library/system.web.ui.page.processrequest.aspx">their documentation</a> actually says:
<blockquote>
<b>You should not call this method.</b>
</blockquote>
Priceless!
</p><p>
Let us proceed to tread into "you have been warned" territory, though. The problem is you (apparently) aren't just supposed to create a new <code>Page</code>. You should use the <code>PageHandlerFactory.</code> Unfortunately, MS has inconveniently laden that thing with an internal constructor, so you can't make one of those, either.
</p><p>
Thanks to <a href="http://www.rvenables.com/2009/08/instantiating-classes-with-internal-constructors/">Robert's C# musings</a> for the answer to this one, which is the key to solving this problem. You can't instantiate a class with an internal constructor, directly. But you can make yourself an instance of any class that doesn't call any constructor using a well-hidden <code>GetUninitializedObject</code> method. Since the constructor in the case of this factory is designed only to prevent us from using the factory, and doesn't actually do antyhing useful, not a problem.
Once you've got access to the handler factory, the rest falls into place pretty easily. Here's some basic code that maps any path to <code>default.aspx</code>, converting the original path to a query parameter.
<pre class="myprettyprint">
public void ProcessRequest(HttpContext context) {
// the internal constructor doesn't do anything but prevent you from instantiating
// the factory, so we can skip it.
PageHandlerFactory factory =
(PageHandlerFactory)System.Runtime.Serialization.FormatterServices
.GetUninitializedObject(typeof(System.Web.UI.PageHandlerFactory));
// you may want to use context.PathInfo - in my case I mapped a wildcard to this
// handler so it's always blank.
string newTarget = "default.aspx";
string newQueryString = "path="+context.Path;
string oldQueryString = context.Request.QueryString.ToString();
string queryString = newQueryString + oldQueryString!="" ?
"&" + newQueryString :
"";
// the 3rd parameter must be just the path to the file target (no querystring).
// the 4th parameter should be the physical path to the file, though it also
// works fine if you pass an empty string - perhaps that's only to override
// the usual presentation based on the path?
var handler = factory.GetHandler(context, "GET",newTarget,
context.Request.MapPath(context,newTarget));
// Update the context object as it should appear to your page/app, and
// assign your new handler.
context.RewritePath(newTarget, "", queryString);
context.Handler = handler;
// .. and done
handler.ProcessRequest(context);
}
</pre>
<p>
The <code>PageHandlerFactory</code> can certainly be created statically so you don't have the overhead of reflection for every request. The actual "Page" handler must be created each time, though, because it's not marked as <code>IsReusable</code>.
</p>Jamiehttp://www.blogger.com/profile/04089108417465569092noreply@blogger.com0tag:blogger.com,1999:blog-2883880512830686149.post-11608283870187970962012-03-12T11:01:00.001-04:002012-03-14T10:58:55.772-04:00SharpLinter now works with inline scripts in HTML files<p>
It will look for embedded scripts and only validate what's inside legal <code><script type="text/javascript"></script></code> blocks. Any file that's not called *.js or *.javascript will be treated this way.
</p>
<p><a target="_blank" href="https://github.com/jamietre/SharpLinter">Go get Sharplinter 1.0.2.</a> You can also <a href="https://github.com/downloads/jamietre/SharpLinter/sharplinter.zip">just download the binary distribution.</a>
</p>
<p>SharpLinter is a C# command-line tool for validating javascript. It is highly configurable, can produce output in customized formats, and integrates with Visual Studio. It should work with any editor or tool that supports using external tools to process files. The readme includes instructions for use with Visual Studio, Sublime Text 2, and Textpad.
<p>
<ul><li><a href="http://blog.outsharked.com/2011/08/sharplinter-command-line-tool-for.html">SharpLinter overview</a></li>
<li><a href="https://github.com/jamietre/SharpLinter">GitHub home</a></li>
<li><a href="http://blog.outsharked.com/search/label/sharplinter">All blog posts about SharpLinter</a></li>
</ul>Jamiehttp://www.blogger.com/profile/04089108417465569092noreply@blogger.com0tag:blogger.com,1999:blog-2883880512830686149.post-61995514072964332672012-03-07T15:48:00.000-05:002012-03-07T17:15:28.127-05:00Area groups in ImageMapster<p>ImageMapster has had the capability to form complex area groups for some time, but never got around to documenting it well. More recently, I added a <code>keys</code> feature that lets you get the keys associated with an area (or another key), and also the ability to pass rendering options directly with the <code>set</code> method. This opens up a lot of possibilities for area manipulation that were possible but not very easy before.
</p><p>
I put together an example showing how area groups can be used, along with these new features, to have a great deal of control over the effects.
</p><p>
<a href="http://jsfiddle.net/eaf2G/" target="_blank">Take a look at it on jsFiddle</a> and play around! I'm going to start a library of interactive examples to include on the web site. If you like it, let me know, or put together one that shows your own techniques so I can share it!<br />
</p>
<p>Complete documentation is <a href="http://www.outsharked.com/imagemapster">on the project web site.</a></p>
<div style="clear: both; float:right;padding:14px;"><a href="http://1.bp.blogspot.com/-UqmAb6HIfqc/T1fJGKe1WkI/AAAAAAAAAGw/4LHwkWWEnig/s1600/groups-demo.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="203" src="http://1.bp.blogspot.com/-UqmAb6HIfqc/T1fJGKe1WkI/AAAAAAAAAGw/4LHwkWWEnig/s320/groups-demo.gif" width="320" /></a></div>
<br />
<h3>
Intro to the ImageMapster Area Groups example:</h3>
<br/>
<div class="box">
<div class="box-inner">
This example shows how to use <code>mapKey</code> to create groups that you can use to control sets of areas independently. <br />
<ul class="accordion">
<li>Each area in the imagemap has a custom attribute <code>data-state</code>. This defines the groups that each area belongs to.</li>
<li>The <code>mapKey: 'data-state'</code> option identifies this attribute for the imagemap. The values in the mapKey can be used to select, deselect or highlight areas or groups of areas from code. </li>
<li>Areas can belong to more than one group. In this example, New England states belong to three groups: a state code like "ME", the group "new-england", and possibly the group "really-cold".</li>
<li>Options can be set for area groups. These options only apply when the group is activated using its group name. Notice if you click a New England state, it's red (like the other states) but when you activate it using "new-england" or "really-cold", it's blue.</li>
<li>When you mouse over an area, the first group in the list determines what gets highlighted. In this example, most states are actually defined by more than one <code>area</code> HTML element. The first value of <code>data-state</code> is the state code, ensuring that when you mouse over a state, all the different areas that make it up get highlighted together, even if they aren't connected. New England states and Hawaii are good examples of this (the islands are separate, and the New England states have separate text markers).</li>
<li>Areas are <i>separate logical entities.</i> You will notice if you click one of the group links, then highlight a state in New England, it highlights again (in a different color, per the <code>render_select</code> options). This means that area groups are not *directly* a good way to just act as if the user selected each area in the group, but...</li>
<li>You can use the <code>keys</code> method to get the primary keys for a group, and the <code>get_options</code> method to get the options, and set them manually. Click "New England As Separate States" below. The "Texas with Custom Options" is a simpler case of setting custom rendering options.</li>
</ul>
</div>
</div>Jamiehttp://www.blogger.com/profile/04089108417465569092noreply@blogger.com1tag:blogger.com,1999:blog-2883880512830686149.post-65567377405908679692012-03-06T08:56:00.000-05:002012-03-06T13:26:27.764-05:00SharpLinter now supports Sublime Text 2Okay, okay. Nothing at all has changed with <a href="http://blog.outsharked.com/2011/08/sharplinter-command-line-tool-for.html">SharpLinter</a> - <a href="https://github.com/jamietre/SharpLinter">github</a>, and Sublime Text 2 supports pretty much anything that generates consistent output with the right config :)<br />
<br />
Here's how to add a build system for Sublime Text 2 that uses SharpLinter for Javasript files.<br />
<ol>
<li>Select <i>Tools -> Build System -> New Build System</i></li>
<li>Enter the following to create a build config that works against javascript
files:</li>
</ol>
<pre class="myprettyprint"><code>{
"cmd": ["sharplinter", "-v","-ph","best","*.min.js", "$file"],
"file_regex": "^(.*?)\\(([0-9]+)\\): ()(.*)$",
"selector": "source.js"
}
</pre>
</code>
<p>Save it, and your're done. The regex should match SharpLinter's default output and let you use <code>F4</code> and <code>shift+F4</code> to navigate any errors within your file.
</p>
<p>
The "cmd" property for Submlime Text 2 should contain the command followed by
any options you want, so this could be as simple as
</p>
<pre class="myprettyprint"><code>...
"cmd": ["sharplinter", "$file"],
...
</code></pre>
<p>to run SharpLinter with default options against the active file. The options above are just the ones I like to use, which provide verbose output, minify to *.min.js on success, and use the best compression method (usually yui).
</p>
<p>Go ahead and set yourself up with <a href="https://github.com/alexnj/SublimeOnSaveBuild">SublimeOnSaveBuild</a> and you can have it run every time you save automatically.</p>Jamiehttp://www.blogger.com/profile/04089108417465569092noreply@blogger.com0tag:blogger.com,1999:blog-2883880512830686149.post-8084432726202297762012-02-16T09:47:00.001-05:002012-02-16T13:24:06.447-05:00Dragging and Dropping onto HTML Image Maps<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.16/jquery-ui.min.js" type="text/javascript"></script><p>
A user of my jQuery plugin <a href="http://www.github.com/jquery/imagemapster">ImageMapster</a>, while working through a problem, asked if it was possible to set up an imagemap that you could drop things onto. I had never done this before, but it seemed like an interesting problem with lots of potential applications, so I set about trying to solve it. A the bottom of this page is a simple example using Marvin the Martian that works, and I'll explain how before we get there.
</p>
<p>
<img src="http://www.outsharked.com/blog/marvin.gif" style="border: 0; z-index: 1000;float:right;" />
Using jQueryUI's <code>draggable</code> method, it's easy to create things that can be dragged. Things get a little tricky because of <code>z-index</code>es though. When you drag something, it needs to have a highest <code>z-index</code> on the page, or it will disappear behind things when you drag it over them. But at the same time, an imagemap will not activate unless <i>it</i> has the highest <code>z-index.</code> A paradox!
</p>
<p>
Luckily, when using ImageMapster, things are already set up so you can have your dragging element on top, and still have an active imagemap. This is the very nature of how it works: when binding an image, ImageMapster makes a copy of the image to use as a backdrop, and then makes your original image invisible though CSS using <code>opacity:0</code>. So all you need to do is make sure the z-index of your drag element is between those two things. A little understanding of ImageMapster's element scheme is all that's needed:</p>
<div style="text-align:center;">
<img src="http://www.outsharked.com/blog/MapsterLayers.gif">
</div>
<p>
The topmost layer, the HTML image map itself, isn't really a layer in that you don't need to set the <code>z-index</code> of the <code><area></code> elements explicitly. However it does act like a layer in that mouse events over the areas will supercede events over the image itself. But basically, the original image is the topmost element.
</p>
<p>
Finally, all this stuff is wrapped up in a div. This is useful to know because it means you can use jQuery's <code>siblings</code> method to easily change the <code>z-index</code> of everything that's important <i>except</i> your original image.
</p>
<p>
The code below is the basic logic you'll need to make something draggable over a live imagemap.</p>
<pre class="myprettyprint">
var img = $('#my-image-map'), item = $('#draggable-object');
img.mapster();
...
// after binding with imageMapster, set the z-index of the image's siblings
// to zero (the lowest). The image copy that is created, as well as the canvases
// that render the highlight effects, are all siblings of the original image
// after binding ImageMapster.
// IMPORTANT: This must be done *after* mapster has finished binding -- see the
// actual code below for the use of "onConfigured" to do this
img.siblings().css('z-index',0);
// set the image itself to something higher.
img.css('z-index',10);
// the draggable element should have a z-index inbetween the visible
// background and effects images (which are all set to 0 now) and the
// original image+imagemap (now set to 10).
item.css('z-index',5);
</pre>
<p>
That's <i>almost</i> all you need to do. What happens when you drop the martian somewhere? It now has a z-index that is lower than the original image. Even though the imagemap is not visible, and the martian is, you won't be able to grab the martian again, because once you drop it, it's old z-index takes effect.
</p>
<p>
To address this, you need a little more sleight of hand. When someone first grabs the draggable element, change its <code>z-index</code> to be a value between the two image layers. When they drop it, though, change it to something that's higher than the original image, so it will be on top and can be picked up again.
</p>
<h3>
In Action!</h3>
<p>
Here's the functioning example, all the code follows (or just use your browser to look at it). Drag the martian onto Mars to "win". Any other planets will give you a negative response, and nothing happens if you drop him in space. I've also set it up on <a href="http://jsfiddle.net/8w42g/" target="_blank">jsfiddle.net</a>. Enjoy!
</p>
<div style="clear: both; height: 20px;">
</div>
<div style="float: left; font-size: 24px; height: 100px;">
<div style="float: left; top: 20px;">
Help me get home!</div>
<img id="martian" src="http://www.outsharked.com/blog/marvin.gif" style="border: 0; -webkit-box-shadow:none;background-color: transparent;border: none;" /></div>
<div style="clear: both;">
</div>
<img id="planets" src="http://www.outsharked.com/blog/planets.jpg" style="opacity: 0.8; z-index: 0;" usemap="#PlanetsMap" />
<br>
<h3>
Code:</h3>
<br>
<i>Internet Explorer notes: This doesn't seem to work in IE < 9 -- in that the imagemap areas are not activated on mouseover while dragging. I'll address this, and present a solution, in my next post.</i>
<pre class="myprettyprint">
var img = $('#planets'), martian=$('#martian');
img.mapster({
mapKey: 'alt',
fillOpacity: 0.8,
fillColor: 'ff0000',
stroke: true,
strokeWeight: 2,
strokeColor: '00ff00',
onConfigured: function() {
// this will be called only after ImageMapster is done setting up
img.siblings().css('z-index',0);
img.css('z-index',10);
}
});
martian.css('z-index',5)
.draggable({
drag: function() {
$(this).css('z-index', 5)
}
});
img.droppable({
drop: function(e,ui) {
// set z-index to the highest, so it can be dragged again
$(ui.draggable).css('z-index',20);
// returns the mapKey value of the currently highlighted item
var landing=img.mapster('highlight');
if (landing=='Mars') {
alert("Thanks for bringing me home!!!");
martian.draggable('disable').hide('slow');
} else if (landing) {
alert("I don't live on " + landing);
}
}
});
</pre>
<map id="Map1" name="PlanetsMap" style="display:none;">
<area alt="Mercury" coords="139,58,122,66,116,84,121,94,124,101,132,107,
140,110,146,111,138,100,134,88,134,78,137,68" href="http://pds.jpl.nasa.gov/planets/choices/mercury1.htm" shape="poly"></area>
<area alt="Venus" coords="176,40,195,44,207,54,217,70,217, 82,210,91,202,102,197,114,196,119,185,121,169,121, 158,117,144,108,137,95,136,82,137,67,145,54,158,43" href="http://pds.jpl.nasa.gov/planets/choices/venus1.htm" shape="poly"></area>
<area alt="Earth" coords="247,128,50" href="http://pds.jpl.nasa.gov/planets/choices/earth1.htm" shape="circle"></area>
<area alt="Moon" coords="340,88,18" href="http://nssdc.gsfc.nasa.gov/planetary/factsheet/moonfact.html" shape="circle"></area>
<area alt="Mars" coords="337,191,36" href="http://pds.jpl.nasa.gov/planets/choices/mars1.htm" shape="circle"></area>
<area alt="Jupiter" coords="391,254,438,269,457,285,473,306, 483,337,483,355,474,383,467,396,456,409,429,428,417,434,400,438, 383,437,368,437,362,431,359,409,351,390,342,375,328,363,316,358,308,356,301, 346,302,331,309,306,323,285,342,267,371,257" href="http://pds.jpl.nasa.gov/planets/choices/jupiter1.htm" shape="poly"></area>
<area alt="Saturn" coords="157,331,175,330,201,341,222,350,240,357, 248,362,263,359,287,355,303,358,331,369,344,384,355,401,359,419,360,432, 365,438,376,442,388,455,401,464,419,482,429,493,433,509,429,515,414,515, 385,505,358,494,333,481,324,488,314,490,308,491,303,478,295,466,282,458, 266,447,257,447,246,445,236,445,229,446,224,434,224,420,223,411,211,400, 197,392,182,379,171,365,162,353,154,339" href="http://pds.jpl.nasa.gov/planets/choices/saturn1.htm" shape="poly"></area>
<area alt="Uranus" coords="207,460,235,446,270,450,297,472,309,500, 306,537,284,562,262,576,236,575,219,570,213,564,222,538,225,509,219,481" href="http://pds.jpl.nasa.gov/planets/choices/uranus1.htm" shape="poly"></area>
<area alt="Neptune" coords="123,520,101" href="http://pds.jpl.nasa.gov/planets/choices/neptune1.htm" shape="circle"></area>
</map><script type="text/javascript">
function finishSetup() {
var img = $('#planets'), martian=$('#martian');
img.mapster({
mapKey: 'alt',
fillOpacity: 0.8,
fillColor: 'ff0000',
stroke: true,
strokeWeight: 2,
strokeColor: '00ff00',
onConfigured: function() {
img.siblings().css('z-index',0);
img.css('z-index',10);
}
});
martian.css('z-index',5).draggable({
drag: function() {
$(this).css('z-index', 5)
}});
img.droppable({
drop: function(e,ui) {
var landing=img.mapster('highlight');
$(ui.draggable).css('z-index',20);
if (landing=='Mars') {
alert("Thanks for bringing me home!!!");
martian.draggable('disable').hide('slow');
} else if (landing) {
alert("I don't live on " + landing);
}
}
});
}
$(document).ready(function() {
if (!$.fn.mapster) {
$.getScript("http://www.outsharked.com/scripts/dev/jquery.imagemapster.1.2.4.040.js",finishSetup);
} else {
finishSetup();
}
});
</script><a href="http://www.codeproject.com" rel="tag" style="display: none;">CodeProject</a>Jamiehttp://www.blogger.com/profile/04089108417465569092noreply@blogger.com1tag:blogger.com,1999:blog-2883880512830686149.post-56976684667191057082011-12-21T18:24:00.003-05:002012-07-13T15:32:17.909-04:00Panasonic UB94 wireless adapter driver<p>
<b>Note: Please look through the comments if you're not using Window 7 64-bit or you can't get it to work.</b> There are a lot of notes with other suggestions. Good luck!
</p>
Got a new Panasonic flat screen TV for Christmas from my wife (she must really love me!!) and it came with this little USB wireless Ethernet adapter, I guess so they can say the TV supports wireless networks. Like any self-respecting nerd I have a wired network in my house and a switch underneath my TV so of course I don't need this. But do I look like the guy that's NOT going to figure out how I can use it on a computer?<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="http://2.bp.blogspot.com/-0SfBvu3vQNE/TvJvzd8Qc6I/AAAAAAAAAGY/wofypAnrdqo/s1600/pana-ub94.jpg" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="181" src="http://2.bp.blogspot.com/-0SfBvu3vQNE/TvJvzd8Qc6I/AAAAAAAAAGY/wofypAnrdqo/s320/pana-ub94.jpg" width="320" /></a></div>
It was a little tricky to track down drivers that work with this so I thought I'd share for future googlers. The device reports itself as a "UB94" when you plug it in. I figured out from some sketchy "driver download"/spam sites that it has an Atheros 7010 chipset, which supports 802.11n. Hey, an upgrade from my old G adapter!<br />
<br />
Atheros doesn't seem to provide reference drivers directly to the public, unfortunately. Some more searching revealed this chipset is shared by the Netgear WNA1100, for which drivers can be downloaded from Netgear. Probably many other devices as well.<br />
<br />
<b>To get this thing to work on Windows 7</b> follow these steps. For Windows XP, per a user's comment, it's almost the same, with one minor change noted in the steps below. I've only tested this on Win 7 though.<br />
<br />
In the interest in not infringing on anyone's copyright I'll just tell you what to edit rather than posting a driver inf file.<br />
<br />
<ol>
<li>Download & install drivers from <a href="http://support.netgear.com/app/products/model/a_id/12995">Netgear</a> for the WNA1100.<br />
<br />
</li>
<li>Locate the driver info file, probably: <br />
<br />
<code>C:\Program Files (x86)\NETGEAR\WNA1100\Driver\WIN764\netathurx.inf</code><br />
<br />
</li>
<li>Under the <code>[Manufacturer]</code> section, add one line:<br />
<br />
<pre>%PANASONIC% = Panasonic, NTamd64
</pre>
<br />
</li>
<li>Add a new section after the section for <code>[VERIZON.NTamd64]</code> (actually it probably doesn't matter where you add this, but this seemed as good a place as any)<br />
<br />
<pre>[PANASONIC.NTamd64]
; DisplayName Section DeviceID
; ----------- ------- --------
%PANASONIC.DeviceDesc.7010% = ATHER_DEV_7010.ndi, USB\VID_04DA&PID_3904
</pre>
<p>For XP, it's the same except the section should be called:
</p>
<pre>[PANASONIC]</pre>
<br />
</li>
<li>At the very end in the <code>[Strings]</code> section add this line:<br />
<br />
<pre>PANASONIC.DeviceDesc.7010 = "Panasonic UB94 USB Adapter"</pre>
<br />
This is the text that will appear in device manager. Feel free to personalize.<br />
</li>
</ol>
<br />
<br />
<br />
<br />
After that, I just went to the broken device in Device Manager and updated the driver, pointing it to the folder above. If you want you can un-install all the Netgear software and just keep the three driver files -- that is all that's needed.<br />
<br />
There, you just saved $19.99!Jamiehttp://www.blogger.com/profile/04089108417465569092noreply@blogger.com51tag:blogger.com,1999:blog-2883880512830686149.post-78341194583376398412011-11-29T10:23:00.001-05:002012-07-20T10:04:05.799-04:00CsQuery 1.0 is imminentIn the last four months I've done a lot of work on <a href="http://blog.outsharked.com/2011/07/csquery-c-jquery-port-implementation.html">CsQuery</a> - <a href="https://github.com/jamietre/CsQuery">on github</a> - a C# jQuery port. I have been using it extensively in a few web site projects and it's quite solid. I've ported most of the jQuery tests that are relevant (dom manipulation, traversing, selection, attributes, utility functions).<br />
<p>Rather than update the list of implemented methods, I've compiled a list of the methods that still remain to be implemented. There are not many. :) Everything else that's not in CsQuery already is browser-DOM specific (e.g. related to events, callbacks, etc.) or is a utility function that I don't think is useful in C#.<br />
<br />
<b>jQuery Methods NOT Implemented In CsQuery</b><br />
<pre> Detach
Empty
NextAll
NextUntil
End
WrapAll
WrapInner
ParentsUntil
NextUntil
OffsetParent
PrevAll
PrevUntil
Prepend
PrependTo
Slice
jquery.Contains
jquery.Grep
</pre><br />
.. plus a few CSS selectors. Additionally, there is extensive support for dynamic/Expando objects using a special <code>JsObject</code> class, and <code>CsQuery.Extend</code> (which works pretty much as you would expect). Though anything that implements <code>IDictionary<string,object></code> can be used as the target for object creation methods. This lets you work with objects in JSON form, or dynamic objects, almost seamlessly, e.g.:<br />
<pre class="prettyprint myprettyprint">// Create a new dom from a string of html
var myDom = CsQuery.Create(html);
// "AttrSet" and "CssSet" are the same as Attr(object) and Set(object) - since in C# we can't
// overload return types. Attr(string) and Css(string) return the values of named items in
// CsQuery. This convention is used for methods that can be passed a string of JSON data.
myDom.Select("div.sidebar")
.AddClass("courier")
.CssSet("{'border': '1px solid black', 'font-weight': 'bold'}");
// create a new anonymous object. You can also use any conventional object or expando object
// as a source parameter in CsQuery.Extend
var data = new { pageName="My Home Page", url="/myhomepage.html"};
// "null" below is a convention for the empty object {}. You can also pass a new expando object,
// this isjust shorthand. The parameters match jQuery.extend. This merges the properties of data,
// and the object created from the JSON string passed. There's also a CsQuery.ParseJSON method
// for explicitly creating a new expando object from JSON. Finally, CsQuery.Extend will work
// with conventional objects as the target (first parameter). In this case, it will only update
// existing properties with the new data, since you can't add properties to an existing non-expando
// object.
dynamic dataExtended = CsQuery.Extend(null,data,"{ 'access':'all' }");
myDom.Data("page",dataExtended).Hide();
myDom.RenderSelection();
// outputs:
// <div class="sidebar courier" style="border: 1px solid black; font-weight: bold; display: none;"
// data-page='{"pageName": "My Home Page", "url": "/myhomepage.html", "access": "all" }'>
// </div>
</pre></p><p>There are still some other features I want to implement, but I am hoping to get some examples together and create a version 1.0 distribution in the next month or so. The code is solid and well tested, and it makes server-side HTML management a joy compared to WebControls, Razor/HTML helpers, and so on, where you have limited control over server-side HTML layout. And your brain can work with HTML exactly the same way on the server as it needs to on the client. Your whole browser DOM is right in front of you. It's great for scraping too.<br />
</p><p>I have not done extensive performance testing, but have done a little. It's easily fast enough for real-time HTML parsing. Of course, if you plan to use it on something serving a thousand pages a second, this might matter, but I suspect most people would find it plenty fast. On my laptop, it can parse a 5 megabyte HTML file with over 100,000 unique nodes (<a href="http://dev.w3.org/html5/spec/Overview.html" target="_blank">the entire HTML 5 spec</a>) into an indexed DOM in 2.5 seconds. Selecting all the DIVs (over 3,300) takes less than 1/100th of a second. Now - 2.5 seconds is an eternity for a web server, but this is meant to be an unrealistic situation, and there would be little reason to parse a big page of static HTML that you had no intention of manipulating. A web page that's 20K, which is more typical, would be less than 1/100th of a second. There's definitely room to make it faster, too, but it's plenty fast now, and I suspect it's a lot faster than manipulating and rendering a page with something like WebControls anyway.<br />
</p><p>Features that I still want to add:<br />
<ul><li><b>Asynchronous HTTP gets</b> - right now when using <code>CsQuery.Server().CreateFromUrl()</code> to load a DOM from the web, code execution is blocked while the get is performed. This is probably fine for some basic web scraping, but will slow things down a lot for any substantive real-time usage. I started coding for an async model but have not finished yet.</li>
<li><b>Form postback management</b> - there's a basic tool for repopulating form elements from their postback data in the Server() module. This needs to be fleshed out and tested a bit, though, because I have not used it too much as I haven't created a lot of conventional HTML forms lately.</li>
<li><b>Framework and view engine</b> - I've developed a useful, simple framework as part of one project. This includes some custom HTML tags like <code><csq-include src="..." /></code>, <code><csq-when [conditions]>...</csq-when></code> to do things like server-side includes, environment-specific includes, and so on. These are not really specific to CsQuery but rather CsQuery is used to implement them, and they make working with pure HTML a lot easier.</li>
<li><b>Templates</b> - something like the jQuery template plugin. Of course it's a piece of cake to write CsQuery code to do simple substitutions, but it would be nice to integrate some of that functionality into a framework.</li>
<li><b>Client script communication</b> - one of the things that CsQuery makes very convenient is preconfiguring data for client-side controls. For example, say you have a grid control. A typical usage might be to initialize the control with an ajax request upon first page load. This causes the page to be rendered with no data at first, then perhaps an ajax loader shown to the user while it gets the default data. Why not pass the first batch of data directly to the control? It's easy to use <code>CsQuery.Data()</code> to pass data as an attribute of an HTML element, then in your javascript, just grab it with <code>jQuery.Data()</code>. This requires using some HTML element as a payload container. Not a big deal, but I would like to standardize this convention and create methods to abstract it.</li>
</ul><br />
Anyway, it's getting close, but feel free to download the project from github and give it a try. The basic usage could not be simpler.<br />
<br />
<pre class="prettyprint myprettyprint"><code>var myDom = CsQuery.Create(htmlString);
var content = myDom["#maincontent > div.title"];
var newContent = myDom["<span>Hello world!</span>"].Css("font-weight","bold");
content.Append(newContent);
Response.Write(content.Render());
</code></pre><br />
</p>Jamiehttp://www.blogger.com/profile/04089108417465569092noreply@blogger.com0tag:blogger.com,1999:blog-2883880512830686149.post-49800490776779645602011-11-10T19:12:00.001-05:002011-11-11T15:38:39.950-05:00How to run NUnit tests in Visual Studio 2010/MSTest<p>There are probably millions of lines of test code written against NUnit, and most people take no joy in switching to MSTest just so they can use Visual Studio's IDE. There is an extension called <a href="http://visualstudiogallery.msdn.microsoft.com/c8164c71-0836-4471-80ce-633383031099">Visual NUnit</a> which adds some support. This is actually a really nice, solid extension, but unfortunately, it doesn't solve the most basic problem: being able to <b>debug</b> tests from directly within your project.<br />
</p><p>MSTest is unfortunately closed, sealed, locked. It's virtually impossible to extend it. But there is a quick and dirty way to get around this problem and have the best of both worlds: NUnit as a testing framework, but still have the ability to run (and debug!) tests from within VS. And you don't have to sacrifice anything - they will still work in any NUnit test runner.<br />
</p><p><b>Step 1: Assert Your Independence</b><br />
</p><p>Add both testing framework namespaces:<br />
<code><br />
using Microsoft.VisualStudio.TestTools.UnitTesting;<br />
using NUnit.Framework;<br />
</code><br />
Create aliases in your namespace declarations:<br />
<code><br />
using Assert = NUnit.Framework.Assert;<br />
using CollectionAssert = NUnit.Framework.CollectionAssert;<br />
using StringAssert = NUnit.Framework.StringAssert;<br />
</code><br />
This causes the Assert references to unambiguously refer to the NUnit version. That covers the objects, then there are all the attributes used to mark things for the framework. Luckily, most attributes do not conflict. <code>Description</code> is an exception, you'll have to pick a framework if you use this attribute, e.g. :<br />
<code><br />
using Description = NUnit.Framework.DescriptionAttribute;<br />
</code><br />
will cause all appearances of Description to be recognized only in NUnit. <br />
</p><p><b>Step 2: Search & Replace</b></p>You need to add all the corresponding MSTest attributes to get the IDE runner to recognize things. Just add both attributes to each class/method, e.g. <code>[TestClass,TestFixture]</code><br />
<br />
<table style="background-color: #dddddd; padding: 8px;"><tr> <th>Function</th><th>NUnit</th><th>Microsoft</th></tr>
<tr><td><br />
Run tests in this class at start (class-level attribute)<br />
</td> <td><br />
[SetupFixture]<br />
</td><td><br />
[AssemblyInitialize]<br />
[AssemblyCleanup]<br />
</td></tr>
<tr><td colspan="3"><br />
<i>(SetupFixture & AssemblyIntialize/AssemblyCleanup work differently - in NUnit a class is marked as [SetupFixture] and has [Setup] and [TearDown] methods. In MS, these just apply to any static methods, with a limit of one each per namespace)</i><br />
</td></tr>
<tr><td><br />
Run once at start per class<br />
</td> <td><br />
[TestFixtureSetUp]<br />
</td><td><br />
[ClassInitialize]<br />
</td></tr>
<tr><td><br />
Run once at end per class<br />
</td> <td><br />
[TestFixtureTearDown]<br />
</td><td><br />
[ClassCleanup]<br />
</td></tr>
<tr><td><br />
Identify a class containing tests<br />
</td> <td><br />
[TestFixture]<br />
</td><td><br />
[TestClass]<br />
</td></tr>
<tr><td><br />
Run before each test<br />
</td> <td><br />
[Setup]<br />
</td><td><br />
[TestInitialize]<br />
</td></tr>
<tr><td><br />
Run after each test<br />
</td> <td><br />
[Cleanup]<br />
</td><td><br />
[TestCleanup]<br />
</td></tr>
<tr><td><br />
... and, of course, A Test<br />
</td> <td><br />
[Test]<br />
</td><td><br />
[TestMethod]<br />
</td></tr>
</table><p><b>Step 3: Instance Setup/Teardown</b></p></p>There are some other differences. The setup types for MS must all be static methods, whereas NUnit allows them to also be instance methods. Recoding everything to use static methods is a headache, so I just do this instead. Chances are, your units tests already inherit from some other class. If so, just change your template class. If not, add one. To fake the NUnit instance startup/teardown methods, just use the constructor and destructor of your base class:<br />
<br />
<pre class="myprettyprint">public class Test()
{
public Test()
{
Setup();
}
~Test() {
TearDown();
}
public virtual void Setup()
{ }
public virtual void TearDown()
{ }
}
</pre>I've basically just skipped out on using any of the framework class-level setup/teardown methods, and use the regular class constructor/destructor instead. In each unit test, you just override Setup and Teardown. You probably do this already if your tests inherit from a base class, this just changes the mechanism by which they are invoked. I haven't thought too much about possible side effects of this, but it would seem to be functionally equivalent.<br />
</p><p><b>Step 4: TestContext</b></p><p>If you happen to be using TestContext, this will be another conflict, since both frameworks have a same-named object. The MS static intitialization methods have it as a parameter, too, whereas for the NUnit framwork, it's a static object you can always access. An easy solution is just to alias the MS one, since you probably haven't written any code against it yet, e.g.:<br />
<code><br />
using MsTestContext = Microsoft.VisualStudio.TestTools.UnitTesting.TestContext;<br />
using TestContext = NUnit.Framework.TestContext;<br />
</code><br />
Now, if you actually want to use the MS static methods, you can just use <code>MsTestContext</code> as a parameter, and <code>TestContext</code> will refer unambiguously to the NUnit one.<br />
</p><p><b>Step 5: Convert to Test Project</b></p><p>Visual Studio won't give you the testing tools until you add this to the <code>.csproj</code> file of your test project. It goes under Project/PropertyGroup.<br />
<br />
<code><ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids></code><br />
<br />
</p><b>Step 6. Develop, Test, Not Necessarily In That Order!</b></p><br />
You are now done. If you've been careful, nothing you've done will in any way break this when running under NUnit, and all these tests will now run directly in the Visual Studio IDE as well.<br />
<br />
<br />
<b>In Summary:</b><br />
<br />
<ul><li>Alias conflicting objects</li>
<li>Use <i>both</i> the NUnit & MS attributes on each class/method as appropriate</li>
<li>Deal with non-implemented instance setup & teardown methods using constructor/destructor</li>
<li>Convert to a test proejct</li>
</ul><br />
... and you should be good to go. While it may take a little bit of work to update large existing test suites, it's mostly search and replace. For new work, just build this into your template, and it's already done.<br />
<br />
<a href="http://www.codeproject.com" rel="tag" style="display: none;">CodeProject</a><br />Jamiehttp://www.blogger.com/profile/04089108417465569092noreply@blogger.com2tag:blogger.com,1999:blog-2883880512830686149.post-14197500471038402752011-11-08T12:35:00.000-05:002011-11-08T12:35:49.667-05:00IE7 & quirks removes trailing space from empty HTML elementsPull up this bad boy in IE7 standards or quirks mode.<br />
<br />
<a href="http://jsfiddle.net/QNs8s/8/">http://jsfiddle.net/QNs8s/8/</a><br />
<br />
Using `innerText` or `innerHTML` causes the space after an element to be erased, e.g. if you take<br />
<br />
<pre>"this is some <span id="field"></span> inline text"</pre><br />
and apply <code>innerText</code> to that span, you get<br />
<br />
<pre>"this is some <span id="field">more</span>inline text"</pre><br />
which renders as<br />
<br />
<pre>this is some moreinline text"</pre><br />
The solution is to start with something inside the span, e.g. <br />
<br />
<pre>"this is some <span id="field">&nbsp;</span>inline text"</pre><br />
I can't believe I've never come across this before, but googling didn't turn up anything about it. Another irritation for supporting old IE.Jamiehttp://www.blogger.com/profile/04089108417465569092noreply@blogger.com1tag:blogger.com,1999:blog-2883880512830686149.post-14393021396766505502011-10-13T13:32:00.000-04:002012-02-16T16:51:41.396-05:00Another new ImageMapster feature: Area ZoomA couple months ago I added the ability to dynamically resize image maps. This made a lot of other things possible, and I've just gotten around to integrating one of them into the codebase. (And I should also mention that the codebase just went over 100K and I will probably fragment out the major features for release 1.3 so you can build a version only with the features you need).<br />
<br />
The new Zoom feature lets you zoom on a specific area. It's pretty limited in terms of features/flexibility right now but without too much code you can do some neat stuff without too much code. Here's a demo I whipped up to let the map "follow" you to zoom in on areas, which could be very useful in presenting a user with an image map that contained some large, but some very small areas.<br />
<br />
<a href="http://jsfiddle.net/kjvkt/14">Fiddle with it</a><br />
<br />
Hover over any area for a second to see it zoom. (If this demo doesn't work, open this post alone -- the include script could conflict with the one from the earlier resize demo)<br />
<br />
There are some issue to be worked out. It's not too hard to make things go completely haywire by mousing all over the place - mouse positioning data is screwed up in blogger, not sure why. The style when zooming shifts things a little bit. But the basic functionality is there.<br />
<br />
<script type="text/javascript" src="http://www.outsharked.com/scripts/jquery.imagemapster.1.2.5b25.js"></script><br />
<div id="container" style="padding:20px;"><div id="map_demo" style="width:440px; height:324px;overflow: hidden; "><br />
<img id="gelderland" src="http://www.outsharked.com/imagemapster/examples/images/Provincie_Gelderland.gif" style="width:400px;height:284px;" usemap="#resize-map"><br />
</div></div><br />
<script type="text/javascript">
$(document).ready(function() {
var cur, opts, map = $('#gelderland'),
width = map.width(),
height = map.height(),
left = map.offset().left,
top = map.offset().top,
zoomOpts = { padding: 60, duration: 500, force: true, scroll: true };
function zoom(key) {
map.mapster('zoom', key, zoomOpts);
}
var inArea,
opts = {
highlight: false,
onMouseover: function (data) {
inArea = true;
cur = data.key;
window.setTimeout(function () {
var newKey = map.mapster('highlight');
if (newKey && newKey === cur) {
zoom(newKey);
}
cur = null;
}, 500);
},
onMouseout: function () {
inArea = false;
}
};
map.mapster(opts);
function zoomOut(e) {
//debugger;
if (!map || !map.mapster('state').zoomed || map.mapster('state').resizing || inArea) {
return;
}
var x=e.pageX-left,
y = e.pageY-top;
if (x<0 || y<0 || x>width || y>height) {
debugger;
map.mapster('zoom',null,zoomOpts);
}
}
$('body').bind('mousemove',zoomOut);
});
</script><br />
<div style="display:none;"><map name="resize-map"><br />
<area href="#" shape="poly" coords="898,654,906,633,900,603,912,593,914,579,919,564,927,562,928,554,937,548,933,541,949,534,959,539,985,540,1000,544,999,550,993,555,997,562,999,574,1008,578,1013,579,1013,585,1020,592" data-alt="Aalten" data-title="Aalten"><br />
<area href="#" shape="poly" coords="496,365,496,351,489,339,498,331,485,317,459,318,445,307,445,299,454,295,452,288,447,282,446,278,466,268,459,250,498,250,501,244,518,250,522,261,544,251,569,245,593,244,597,233,604,234,603,244,623,243,627,245,630,252,626,268,631,283,626,290,635,295,645,318,641,330,649,351,648,359,656,374,640,393,635,398,628,410,628,419,614,440,593,436,583,440,581,438,578,417,554,414,551,411,546,411,527,391,525,367,520,365" data-alt="Apeldoorn" data-title="Apeldoorn"><br />
<area href="#" shape="poly" coords="491,511,496,494,516,467,523,461,530,466,550,466,550,452,558,452,576,438,583,442,576,452,571,467,574,501,585,520,604,536,585,544,583,562,572,553,569,562,562,562,558,567,558,569,553,581,546,579,534,572,525,568,509,568,508,553,512,540,529,546,532,539,529,534,527,518" data-alt="Arnhem" data-title="Arnhem"><br />
<area href="#" shape="poly" coords="295,353,299,345,297,338,307,328,314,331,323,325,325,328,325,335,332,342,348,338,348,331,352,330,352,325,369,323,384,325,393,309,410,295,418,295,421,278,438,281,447,273,447,283,453,295,449,297,449,307,460,323,485,321,495,330,491,339,494,348,496,366,473,365,466,367,456,367,440,370,440,379,432,381,424,379,418,384,410,381,353,394,344,396,337,405,338,412,325,417,318,414,316,417,303,403,314,404,306,396,306,387,316,381,307,374,304,370,297,367" data-alt="Barneveld" data-title="Barneveld"><br />
<area href="#" shape="poly" coords="854,424,864,425,867,414,885,405,891,408,898,405,900,412,909,390,905,373,893,360,903,353,910,337,927,332,966,331,976,339,975,344,978,346,980,349,992,339,1001,344,1003,337,1018,349,1021,369,1013,380,1049,394,1069,391,1069,411,1064,417,1064,424,1059,433,1035,439,1029,454,1027,467,1013,459,1013,449,1007,446,999,454,987,446,985,442,978,445,978,457,971,461,972,470,957,480,940,485,926,468,921,467,895,489,877,485,871,466,865,464" data-alt="Berkelland" data-title="Berkelland"><br />
<area href="#" shape="poly" coords="412,647,419,616,433,620,452,628,461,628,478,635,489,635,503,645,503,649,506,651,501,662,495,661,482,670,475,668,468,679,447,673,415,668,419,663,417,654,412,649" data-alt="Beuningen" data-title="Beuningen"><br />
<area href="#" shape="poly" coords="680,467,717,438,717,422,729,421,739,419,748,412,748,408,755,400,769,404,776,401,781,391,808,374,827,377,827,384,832,388,851,381,849,388,851,394,849,398,870,417,865,422,856,424,856,431,863,452,867,466,877,482,877,487,892,489,893,503,891,505,893,511,891,512,900,522,896,533,886,540,865,537,856,527,846,533,849,541,834,541,839,534,811,534,815,525,806,516,791,522,791,518,776,527,767,522,762,525,767,530,764,536,753,522,748,527,750,533,741,533,711,533,710,513,710,509,711,499,719,494,717,474,710,475,705,468,700,468,694,480,682,475" data-alt="Bronckhorst" data-title="Bronckhorst"><br />
<area href="#" shape="poly" coords="616,447,616,440,631,418,631,410,635,401,641,393,654,384,659,377,656,372,697,358,697,367,701,372,714,367,719,379,724,391,724,400,736,401,743,405,745,415,739,421,731,418,726,424,717,419,715,433,705,442,696,453,689,447,686,449,684,445,672,433,661,431,654,424,634,438,628,445,621,447,617,447" data-alt="Brummen" data-title="Brummen"><br />
<area href="#" shape="poly" coords="151,567,161,562,158,560,167,548,187,553,196,560,208,560,219,544,237,546,258,539,266,530,278,530,283,532,300,532,318,544,331,546,337,555,346,560,358,562,360,567,351,574,342,569,339,571,327,569,334,579,341,578,338,592,307,590,304,595,300,589,293,590,283,589,271,595,259,600,255,602,245,616,210,640,206,631,196,633,201,624,194,616,184,613,171,593,175,590,175,583,173,582,164,581,158,576" data-alt="Buren" data-title="Buren"><br />
<area href="#" shape="poly" coords="94,583,114,547,129,555,135,555,149,534,165,550,151,568,157,578,133,593,130,588,100,595,102,588,100,585" data-alt="Culemborg" data-title="Culemborg"><br />
<area href="#" shape="poly" coords="679,515,679,508,689,503,690,496,690,480,700,475,700,471,708,475,718,475,719,492,711,496,710,508,696,515" data-alt="Doesburg" data-title="Doesburg"><br />
<area href="#" shape="poly" coords="700,532,712,533,753,536,748,525,752,523,755,529,764,537,769,530,763,527,769,523,777,527,790,518,792,523,804,518,811,520,816,525,811,536,836,534,834,541,850,543,847,532,856,530,864,536,843,574,844,576,832,588,815,585,804,574,797,571,797,567,778,568,748,567,736,568,736,575,728,569,724,564,717,560,705,551,703,539" data-alt="Doetinchem" data-title="Doetinchem"><br />
<area href="#" shape="poly" coords="339,649,339,642,349,635,346,628,356,621,338,624,325,620,352,611,380,616,401,617,419,619,419,628,410,644,410,647,393,647,390,661,394,669,374,676,367,673,358,675,356,662,360,656,356,652,355,647" data-alt="Druten" data-title="Druten"><br />
<area href="#" shape="poly" coords="592,575,604,569,604,540,614,534,624,536,627,541,633,541,640,533,648,532,654,533,654,537,648,539,648,551,642,560,649,565,644,572,644,582,640,586,640,593,633,597,630,596,619,595,607,600,595,586,597,582" data-alt="Duiven" data-title="Duiven"><br />
<area href="#" shape="poly" coords="344,466,346,439,346,432,344,431,334,407,345,394,352,397,412,383,417,387,421,380,438,381,440,379,445,372,454,367,466,370,475,365,520,365,526,372,527,393,541,410,548,411,558,415,581,417,579,438,555,452,550,452,548,464,540,466,520,461,513,468,496,492,454,495,450,513,418,520,379,520,377,513,372,519,365,511,362,501,363,481,349,473,351,466" data-alt="Ede" data-title="Ede"><br />
<area href="#" shape="poly" coords="461,114,496,83,495,80,496,65,509,48,534,67,529,74,551,102,557,107,551,109,562,126,553,135,534,160,529,156,527,158,501,130,501,123,496,118,494,116,481,122,480,119,475,121,471,123" data-alt="Elburg" data-title="Elburg"><br />
<area href="#" shape="poly" coords="527,167,541,151,554,135,581,121,628,150,628,156,634,156,635,163,640,157,656,156,666,160,654,167,654,188,642,187,630,224,634,240,635,250,628,245,628,241,603,241,604,231,602,231,592,244,572,241,536,252,523,259,518,241,520,238,515,230,516,226,512,215,520,210,534,191,534,189,537,182" data-alt="Epe" data-title="Epe"><br />
<area href="#" shape="poly" coords="337,229,339,206,352,195,372,203,380,191,388,194,415,217,418,210,431,189,459,202,454,231,461,238,459,247,466,271,433,279,417,276,405,262,398,250,377,243,358,240" data-alt="Ermelo" data-title="Ermelo"><br />
<area href="#" shape="poly" coords="73,626,93,586,100,586,104,588,100,595,128,588,132,595,158,581,164,582,175,583,175,590,170,595,184,613,187,613,201,626,194,630,196,634,202,633,210,637,203,647,203,655,196,654,191,661,175,656,163,655,146,656,137,656,133,661,128,661,121,665,94,666,95,649,86,635,77,642,74,635,76,631" data-alt="Geldermalsen" data-title="Geldermalsen"><br />
<area href="#" shape="poly" coords="546,731,546,718,540,703,546,701,541,693,557,682,565,691,581,696,581,700,599,712,597,721,597,726,609,726,604,734,604,738,609,742,586,760,586,766,575,769,574,767,574,760,564,759,560,748" data-alt="Groesbeek" data-title="Groesbeek"><br />
<area href="#" shape="poly" coords="344,203,358,161,370,156,374,161,394,149,398,156,414,142,426,161,426,164,429,177,435,180,435,187,414,217,386,191,380,191,374,201,363,201,352,194" data-alt="Harderwijk" data-title="Harderwijk"><br />
<area href="#" shape="poly" coords="610,21,613,14,621,17,624,11,641,22,649,36,668,53,668,65,666,67,665,63,654,67,652,66,628,74,619,74,630,55,631,43,624,36,617,36,619,29" data-alt="Hattem" data-title="Hattem"><br />
<area href="#" shape="poly" coords="579,121,588,115,620,76,631,76,654,69,662,65,668,66,668,74,677,84,680,94,675,97,677,104,675,112,687,121,684,123,683,136,656,146,654,157,640,157,634,161,634,154,628,157,628,151,624,150,604,142" data-alt="Heerde" data-title="Heerde"><br />
<area href="#" shape="poly" coords="447,736,452,732,454,734,473,731,473,726,478,726,489,717,499,719,508,714,512,705,513,708,518,703,525,704,533,701,539,701,539,705,541,712,544,726,544,732,533,736,534,750,532,753,527,750,495,753,481,757,468,757,454,748,452,741" data-alt="Heumen" data-title="Heumen"><br />
<area href="#" shape="poly" coords="1,652,36,656,43,654,38,647,38,640,53,640,58,626,72,624,74,633,79,642,86,640,95,651,95,663,98,668,94,679,100,696,86,696,58,684,38,690,18,691,22,670,4,666,4,658" data-alt="Lingewaal" data-title="Lingewaal"><br />
<area href="#" shape="poly" coords="525,616,536,616,539,611,534,609,534,603,546,604,550,590,546,586,550,579,557,567,562,560,571,561,572,553,585,565,595,582,595,588,610,609,626,616,628,627,638,640,619,630,610,634,602,645,597,656,583,655,576,642,562,637,547,641,544,647,541,644,536,628,533,621,527,623" data-alt="Lingewaard" data-title="Lingewaard"><br />
<area href="#" shape="poly" coords="710,286,719,282,724,288,742,290,742,288,764,289,770,288,780,283,787,285,792,290,815,289,813,281,822,271,830,273,837,266,844,271,863,273,863,275,868,290,886,311,893,311,899,328,910,337,905,353,891,360,898,369,907,381,905,398,902,410,898,405,891,405,884,403,879,407,870,414,849,400,853,393,849,388,850,380,833,388,827,384,827,377,811,374,808,360,774,365,755,367,750,358,741,356,735,349,731,351,726,346,726,337,721,335,714,325,705,325,708,314,719,311,726,302,722,293" data-alt="Lochem" data-title="Lochem"><br />
<area href="#" shape="poly" coords="108,748,118,746,129,742,165,738,165,719,175,712,174,691,191,689,203,700,220,707,236,689,245,675,257,689,250,691,224,731,222,746,222,755,184,776,174,770,167,770,154,778,142,769,135,766,123,771,114,769,115,756" data-alt="Maasdriel" data-title="Maasdriel"><br />
<area href="#" shape="poly" coords="624,670,619,668,619,663,628,656,628,647,620,640,604,642,611,633,617,630,628,633,640,641,652,648,647,654,645,656,634,668" data-alt="Millingen aan de Rijn" data-title="Millingen aan de Rijn"><br />
<area href="#" shape="poly" coords="668,574,668,548,666,548,666,544,675,546,686,541,684,539,698,533,703,537,703,541,710,557,717,560,721,564,739,574,738,569,752,567,797,567,797,572,792,575,788,583,791,589,802,589,804,592,804,597,798,602,802,610,811,614,813,616,812,621,813,633,815,634,812,640,794,642,780,635,771,641,752,640,743,642,725,626,728,619,707,603,708,599,703,600" data-alt="Montferland" data-title="Montferland"><br />
<area href="#" shape="poly" coords="286,624,281,610,288,604,283,602,285,595,282,589,295,593,300,588,302,595,304,596,307,590,339,595,342,579,335,578,330,571,330,571,338,572,345,569,351,574,360,564,374,574,388,576,394,574,405,569,410,574,404,578,404,585,426,581,418,585,417,595,425,595,431,602,433,609,436,620,410,619,398,616,369,616,358,611,341,613,325,621,306,623" data-alt="Neder-Betuwe" data-title="Neder-Betuwe"><br />
<area href="#" shape="poly" coords="97,663,118,666,135,662,143,656,144,658,171,656,192,663,198,655,210,655,231,649,244,659,257,661,238,682,224,705,210,705,195,690,187,687,163,696,146,697,128,703,115,698,108,694,100,697,101,689,95,682" data-alt="Neerijnen" data-title="Neerijnen"><br />
<area href="#" shape="poly" coords="243,297,250,271,295,259,296,279,310,285,317,285,338,314,387,310,388,316,384,324,379,325,372,318,353,323,353,330,346,330,348,339,337,344,330,334,330,330,327,325,318,325,316,330,311,328,296,334,297,346,295,352,272,346,271,314,254,299,250,303" data-alt="Nijkerk" data-title="Nijkerk"><br />
<area href="#" shape="poly" coords="466,689,474,668,482,672,496,661,501,665,506,651,503,642,508,640,509,642,513,630,509,620,522,616,526,624,536,626,536,635,546,648,546,658,541,668,548,677,555,683,541,690,541,696,544,703,532,703,513,704,506,710,499,719,487,712,474,689" data-alt="Nijmegen" data-title="Nijmegen"><br />
<area href="#" shape="poly" coords="415,142,424,142,461,115,468,125,480,121,482,123,495,119,501,123,501,129,527,158,534,161,527,168,537,184,534,188,534,192,519,210,512,215,516,236,518,243,518,250,518,251,501,243,499,245,457,247,461,241,461,236,456,231,459,202,432,191,435,188,436,180,432,180,426,163,428,161,426,158,425,158" data-alt="Nunspeet" data-title="Nunspeet"><br />
<area href="#" shape="poly" coords="513,50,518,34,518,28,525,4,534,1,562,48,581,36,588,43,611,22,619,29,619,38,624,38,628,46,628,60,621,74,620,74,589,112,576,118,579,121,562,126,553,109,558,107,547,98,530,76,532,72,534,67" data-alt="Oldebroek" data-title="Oldebroek"><br />
<area href="#" shape="poly" coords="896,532,902,523,892,513,898,511,893,503,895,503,892,492,902,480,906,480,912,473,917,474,919,467,926,470,940,484,958,480,968,471,973,467,973,461,979,459,980,445,982,442,992,446,993,450,999,453,1006,446,1013,447,1013,457,1029,466,1027,480,1015,489,1010,495,1008,511,1013,532,1001,544,992,541,971,536,952,534,934,541,919,540" data-alt="Oost Gelre" data-title="Oost Gelre"><br />
<area href="#" shape="poly" coords="799,659,794,641,813,641,812,620,813,616,806,611,799,602,804,596,804,593,801,590,795,590,790,583,794,571,802,574,816,585,833,588,846,578,844,574,865,537,886,540,896,532,920,541,933,541,937,546,928,554,927,560,921,562,913,579,913,588,900,603,905,611,905,623,907,630,905,638,898,654,886,641,878,647,872,642,863,637,856,638,851,634,849,645,863,655,858,663,863,670,858,673,860,682,849,675,839,675,833,663,823,656,811,656" data-alt="Oude IJsselstreek" data-title="Oude IJsselstreek"><br />
<area href="#" shape="poly" coords="396,560,408,561,419,555,440,550,453,544,460,544,475,553,481,553,503,541,513,548,508,553,509,568,522,571,534,569,548,579,546,586,548,592,547,596,546,606,534,604,534,611,534,616,506,619,512,628,509,638,503,642,489,634,477,635,460,627,452,627,435,620,438,616,433,616,435,604,424,593,417,593,417,586,425,582,424,578,405,581,405,576,410,574,407,569,396,574" data-alt="Overbetuwe" data-title="Overbetuwe"><br />
<area href="#" shape="poly" coords="296,258,330,248,337,229,337,231,360,241,372,241,373,244,393,247,403,258,417,272,417,278,417,292,411,295,387,309,338,313,316,282,310,285,304,281,299,281,295,278" data-alt="Putten" data-title="Putten"><br />
<area href="#" shape="poly" coords="454,495,495,491,489,511,508,518,527,519,527,534,532,539,527,541,505,541,482,553,475,553,461,546,454,543,445,551,439,548,442,534,440,533,443,513,452,513" data-alt="Renkum" data-title="Renkum"><br />
<area href="#" shape="poly" coords="597,461,611,461,616,449,626,447,652,426,659,431,677,438,683,446,696,454,684,461,677,474,682,480,691,489,689,502,683,509,680,518,668,519,659,511,661,502,654,496,638,505,638,512,633,518,621,518,616,522,614,534,604,540,588,525,582,513,597,508,600,496,609,489" data-alt="Rheden" data-title="Rheden"><br />
<area href="#" shape="poly" coords="609,602,624,596,630,595,638,597,670,602,669,604,677,616,675,620,684,619,697,623,700,642,715,649,712,652,715,669,696,662,675,663,652,644,645,642,635,638,630,628,627,616,621,609" data-alt="Rijnwaarden" data-title="Rijnwaarden"><br />
<area href="#" shape="poly" coords="575,502,572,466,576,452,585,442,607,436,617,440,616,447,614,457,609,460,599,459,609,489,599,496,599,509,590,512,583,512" data-alt="Rozendaal" data-title="Rozendaal"><br />
<area href="#" shape="poly" coords="283,435,299,425,299,411,304,405,323,417,330,419,330,428,328,435,313,440,297,447" data-alt="Scherpenzeel" data-title="Scherpenzeel"><br />
<area href="#" shape="poly" coords="203,655,203,647,247,616,257,602,266,592,283,593,283,600,286,606,279,611,286,624,272,630,259,647,255,661,243,659,233,649,223,655" data-alt="Tiel" data-title="Tiel"><br />
<area href="#" shape="poly" coords="541,668,546,652,541,654,553,640,569,640,583,654,596,659,603,654,604,642,621,641,627,645,628,654,627,661,619,663,621,672,616,677,602,680,590,675,581,686,585,696,581,697,562,690,554,680,548,675,550,670" data-alt="Ubbergen" data-title="Ubbergen"><br />
<area href="#" shape="poly" coords="630,224,641,187,654,188,648,196,654,201,658,205,659,215,670,217,675,229,677,238,686,257,684,264,698,268,701,272,696,281,708,295,712,288,728,302,719,311,710,316,707,321,711,327,722,337,728,346,724,358,698,358,659,370,649,359,649,348,642,332,645,321,640,296,631,292,630,283,626,268,633,250,634,247,635,238" data-alt="Voorst" data-title="Voorst"><br />
<area href="#" shape="poly" coords="372,569,387,562,381,548,386,541,377,533,380,526,373,520,376,516,381,520,410,519,417,525,419,520,442,515,439,532,440,534,438,546,440,550,431,550,414,555,407,561,396,561,396,572,387,576" data-alt="Wageningen" data-title="Wageningen"><br />
<area href="#" shape="poly" coords="248,676,258,655,268,637,281,626,304,626,328,619,337,626,355,619,341,649,352,649,359,655,356,661,360,673,373,676,373,680,370,683,362,680,355,684,339,684,337,696,318,693,309,682,299,683,295,691,288,700,275,703,262,698" data-alt="West Maas en Waal" data-title="West Maas en Waal"><br />
<area href="#" shape="poly" coords="583,562,583,547,602,540,604,569,590,575" data-alt="Westervoort" data-title="Westervoort"><br />
<area href="#" shape="poly" coords="372,684,374,677,394,668,393,661,393,656,393,645,412,647,419,663,415,666,468,680,464,689,473,690,487,715,492,717,478,728,474,726,473,729,454,734,452,731,447,738,435,734,425,722,422,719,410,719,403,710,396,694,381,691" data-alt="Wijchen" data-title="Wijchen"><br />
<area href="#" shape="poly" coords="1029,470,1048,468,1105,508,1102,511,1114,513,1118,536,1116,541,1111,541,1100,547,1097,567,1081,589,1073,590,1070,599,1063,602,1055,610,1035,592,1028,588,1022,593,1013,581,1008,578,997,572,999,561,996,558,1001,548,1001,544,1010,532,1010,508,1013,499,1011,495,1024,480,1028,481" data-alt="Winterswijk" data-title="Winterswijk"><br />
<area href="#" shape="poly" coords="11,696,31,696,53,687,84,700,104,696,133,703,173,689,175,712,165,719,165,736,109,748,114,752,114,769,81,773,80,767,88,757,87,750,88,739,79,734,63,722,55,724,50,734,45,729,35,717,13,705" data-alt="Zaltbommel" data-title="Zaltbommel"><br />
<area href="#" shape="poly" coords="613,534,614,525,621,516,630,518,640,511,640,505,647,499,661,505,661,513,665,520,677,518,680,515,698,516,703,513,708,513,710,532,691,533,686,537,684,541,676,546,666,543,665,548,668,548,668,574,710,604,710,604,689,613,686,610,677,614,669,606,670,602,662,597,652,597,640,597,637,596,637,583,642,581,649,562,647,560,649,540,654,537,655,532,640,532,633,543,627,541,626,534" data-alt="Zevenaar" data-title="Zevenaar"><br />
<area href="#" shape="poly" coords="697,369,743,353,755,367,771,367,806,363,808,374,780,388,773,401,769,403,750,398,748,405,748,412,743,412,741,403,736,398,724,398,722,388,721,380,715,367,698,367,698,356,715,355,719,353,729,353" data-alt="Zutphen" data-title="Zutphen"><br />
</map><br />
<br />
</div>Jamiehttp://www.blogger.com/profile/04089108417465569092noreply@blogger.com4