This past week we started experimenting with a "long distance collaboration tool" called CardMeeting. Our team has a big ocean in between them which sometimes makes it difficult for everyone to stay on the same page. As I mentioned in How to prevent an agile face plant post, we use index cards for all of our planning. We have a big visible whiteboard that "holds" all of our cards. As we start a task we "brown it", and when we finish it we "green it". At times the card can go red as well which means its blocked due to something development can't control (such as needing clarification from business).
Our whiteboard has a lot of valuable information. At any given time anyone can walk over to the whiteboard and see where we're at. Unless of course they're across the ocean. To help keep our colleagues across the pond in the loop this past week we used CardMeeting to hold a duplicate "copy" of all of our cards. This allowed everyone on the project to have the latest and greatest information about where we were. For the development team it was a bit of a pain maintaining two copies of all the cards, but, being the great team that we are we managed.
In addition to using it to track the current iteration we've also started a couple of other card meetings which are being used to plan out future iterations. I'm not exactly sure how we'll use it going forward, and if it will be useful or not, but at the very least it's an interesting experiment.
tags: agile, agileplanning, taskbreakdown
Although Scoble already received a lot of answers on how he can get get what he wants out of his Excel file containing all the URL's from the http://weblogs.com/changes.xml file I couldn't help but fire up VS and solve his problem using Linq to XML. I'm sure Scoble will send me a big fat check for solving his problem with one of the cooler technologies that will coming out of his former employer. Of course I didn't give him just the URL's that he asked for either so maybe I shouldn't hold my breath. On the other hand maybe he'll give me a bonus for giving him a nice list that has the number of sites for each site he's interested in grouped with a count?
Output:
- Blogspot has 8928 sites in the changes.xml file
- Spaces has 900 sites in the changes.xml file
- Wordpress has 384 sites in the changes.xml file
- TypePad has 118 sites in the changes.xml file
Code:
1 using System;
2 using System.Collections.Generic;
3 using System.Text;
4 using System.Query;
5 using System.Xml.XLinq;
6 using System.Data.DLinq;
7
8 namespace ScobleWeblogsDotComCleaner {
9 class Program {
10 static void Main(string[] args) {
11 XElement weblogs = XElement.Load("http://rpc.weblogs.com/changes.xml");
12
13 var matches =
14 from weblog in weblogs.Elements("weblog")
15 where IsMatch((string) weblog.Attribute("url"))
16 group weblog by GetSite((string) weblog.Attribute("url")) into sites
17 orderby sites.Count() descending
18 select new {
19 HostingSite=sites.Key,
20 Count=sites.Count(),
21 Sites=sites
22 };
23
24 foreach(var match in matches) {
25 Console.WriteLine(match.HostingSite + " has " + match.Count + " sites in the changes.xml file");
26 foreach(var x in match.Sites) {
27 Console.WriteLine(" - " + (string) x.Attribute("url"));
28 }
29 }
30 }
31
32 private static string GetSite(string url) {
33 if(url.Contains("spaces.live.com")) return "Spaces";
34 else if(url.Contains("typepad.com")) return "TypePad";
35 else if(url.Contains("blogspot.com")) return "Blogspot";
36 else if(url.Contains("wordpress.com")) return "Wordpress";
37 return String.Empty;
38 }
39
40 private static bool IsMatch(string url) {
41 return
42 url.Contains("spaces.live.com") ||
43 url.Contains("typepad.com") ||
44 url.Contains("blogspot.com") ||
45 url.Contains("wordpress.com");
46 }
47 }
48 }
tags: linqtoxml, xlinq, linq, scoble
To help get more familiar with the new XML Programming API that is hidden away inside of LINQ to XML I recently set on a journey to update the code that creates my RSS feed to use LINQ to XML. In order to complete my journey I needed to get familiar with how to use functional construction to build XML from a set of in memory objects. Before diving into the LINQ to XML code let's first take a peak at the old code which was building the XML using an XmlWriter.
1 StringWriter sw = new StringWriter();
2 XmlTextWriter xml = new XmlTextWriter(sw);
3 xml.WriteProcessingInstruction("xml-stylesheet", "href=\"" + GetRootUrl() + "/friendly-rss.xsl\" type=\"text/xsl\" media=\"screen\"");
4 xml.WriteStartElement("rss");
5 xml.WriteAttributeString("version", "2.0");
6 xml.WriteAttributeString("xmlns:dc", "http://purl.org/dc/elements/1.1/");
7 xml.WriteAttributeString("xmlns:slash", "http://purl.org/rss/1.0/modules/slash/");
8 xml.WriteAttributeString("xmlns:wfw", "http://wellformedweb.org/CommentAPI/");
9 xml.WriteStartElement("channel");
10 xml.WriteElementString("title", channel.DisplayName));
11 xml.WriteElementString("link", GetRootUrl() + "/" + channel.Url);
12 xml.WriteElementString("generator", "ActiveType CMS v0.1");
13 xml.WriteElementString("dc:language", "en-US");
14 xml.WriteElementString("description", channel.Description);
15
16 foreach(Posting posting in postings) {
17 xml.WriteStartElement("item");
18 xml.WriteElementString("dc:creator", posting.ActiveRevision.LastModifiedBy.FullName);
19 xml.WriteElementString("title", posting.ActiveRevision.DisplayName);
20 xml.WriteElementString("link", postLink);
21 xml.WriteElementString("pubDate", posting.ActiveRevision.CreatedDate.ToString("r"));
22 xml.WriteElementString("guid", postLink);
23 xml.WriteElementString("comments", postLink + "#comments");
24 xml.WriteElementString("wfw:commentRss", postLink + "/commentRss.aspx");
25 xml.WriteElementString("slash:comments", posting.NumberOfComments.ToString());
26 WriteDescriptionElement(xml, true, posting.ActiveRevision.AllowContentFiltering, posting.ActiveRevision.Content, shareLinks);
27 xml.WriteEndElement();
28 }
29
30 xml.WriteEndElement();
31 xml.WriteEndElement();
32 xml.Close();
I've re-organized the code for this post but most of the basics have stayed intact. As you can see we use an XmlWriter (XmlTextWriter to be more specific) to write our root rss element, our main channel, as well as the 25 most recent postings. The LINQ to XML code looks somewhat similar.
1 XNamespace dc = "http://purl.org/dc/elements/1.1/";
2 XNamespace slash = "http://purl.org/rss/1.0/modules/slash/";
3 XNamespace wfw = "http://wellformedweb.org/CommentAPI/";
4
5 XDocument rss = new XDocument(
6 new XProcessingInstruction("xml-stylesheet", "href=\"" + GetRootUrl() + "/friendly-rss.xsl\" type=\"text/xsl\" media=\"screen\""),
7 new XElement("rss", new XAttribute("version", "2.0"),
8 new XElement("channel",
9 new XElement("title", channel.DisplayName)),
10 new XElement("link", GetRootUrl() + "/" + channel.Url),
11 new XElement("generator", "ActiveType CMS v0.1"),
12 new XElement(dc + "language", "en-US"),
13 new XElement("description", GetChannelDescription(channel)),
14
15 from p in postings.OfType<Posting>()
16 where p.ActiveRevision.IsSyndicated && p.ActiveRevision.Content.Length > 0
17 select new XElement("item",
18 new XElement(dc + "creator", p.ActiveRevision.LastModifiedBy.FullName),
19 new XElement("title", p.ActiveRevision.DisplayName),
20 new XElement("link", GetLink(p)),
21 new XElement("pubDate", p.ActiveRevision.CreatedDate.ToString("r")),
22 new XElement("guid", GetLink(p)),
23 new XElement("comments", GetLink(p) + "#comments"),
24 new XElement(wfw + "commentRss", GetLink(p) + "/commentRss.aspx"),
25 new XElement(slash + "comments", p.NumberOfComments.ToString()),
26 GetDescription(p)
27 )
28 )
29 )
30 );
The main difference is that we use functional construction to build our XML using a single statement (albeit spread across many lines) rather then writing each element/attribute using the XmlWriter. It should also be noted that the sample code using the XmlWriter had to be simplified a good bit in order to fit within this post and be readable. The LINQ to XML code hasn't been changed at all (well mostly). The above code shows a couple things which should be pointed out.
- To create elements that use namespaces we create an XNamespace and append the XNamespace to the local name for the element. Notice that the XNamespace has an implicit conversion from a string and can be added to the local name to create an XName. (new XElement(dc + "language", "en-US"))
- Notice that we can create XElements using either hard coded strings or via method calls.
- Since our query (line 15-16) returns an IEnumerable<XElement> and the XElement constructor is capable of taking an IEnumerable<XElement> and adding each item as a child element we can directly embed a query in the middle of our functionally constructed document.
- Even though we're using old school collections in this code (postings is a custom collection that inherits from ArrayList) we can use the collection as the basis of our query by way of the OfType<T>() extension method. The OfType extension method converts an old school collection into an IEnumerable<T> which makes it possible to use it in the from clause of our query.
- The resulting XML will not have xml namespace prefixes. In order to get prefixes output we need to do some other work which I wasn't up for just yet. Rather then outputting <dc:language>en-US</dc:language> we'll get <language xmlns="http://purl.org/dc/elements/1.1/">en-US</language>. I'll talk more about namespaces and namespaces prefixes in another post so if your interested in how to get LINQ to XML to serialize your XML using prefixes stay tuned.
- In addition to adding elements by newing up XElements we can also create a function that returns an XElement and pass that to our constructor and get it added as a child element (line 26).
In future posts I'll dive into some more details about how LINQ to XML improves upon the imperative approach provided by the DOM when creating Xml from scratch, overview how LINQ to XML handles namespaces and namespaces prefixes, as well as introduce some of the key concepts and design goals of LINQ to XML.
tags: linq, xlinq, linqtoxml, xml
Over the last couple of weeks I've been reading a lot of the documentation that
Eric White
put together for XLinq. Eric is a "Programmer Writer" for several Xml
technologies which makes him responsible for a good bit of the
documentation on XLinq.
Yesterday
Eric posted a nice writeup of how he managed to parse WordML using XLinq.
While Eric's writeup shows a lot of the nice features within XLinq I'd
like to hightlight a couple. The obvious first feature to talk about
is how nice the querying capabilities are for Xml when using XLinq. I
think we've all come to know and love the querying that Linq provides
so lets move onto some other nice features of XLinq as highlighted in
Eric's post.
Let's take a look at the query used to retrieve all the annotations in
the Word document. Notice that rather then having to deal with
XmlConvert we can simply cast our attribute to a string. Explicit cast
operators have been defined for the classes that you'll make use of
most when working with XLinq (XAttribute, XElement). They allow
programmers to work with data within an Xml fragment in a very familiar
manner. Rather then having to work with the XmlConvert class
everywhere that we read content out of an attribute or element we can
simple cast the value to the proper type and let XLinq handle all the
dirty work.
var commentNodes =
from annos in wordDoc.Descendants(aml +"annotation")
where (string)annos.Attribute(w + "type") == "Word.Comment"
select annos;
Another nicety to point out is how easy it is to deal with the
namespaces that are used in WordML. If we were using existing Xml
API's we'd have to new up a namespace manager, setup our namespaces and
then make sure everything that "queries" our document uses the
mentioned namespace manager. Not a huge deal, but more painful then
necessary. Within XLinq we can forget about namespace managers since
all "Xml Names" within XLinq are fully qualified. This means that
whenever we're querying our document we need to give the full namespace
and local name of the element or attribute which we're interested in.
In Eric's sample code we can see how we deal with namespaces by looking
at how their defined and then how they're used when querying the
document. Lets start with a look at how the namespaces are defined:
XNamespace aml = "http://schemas.microsoft.com/aml/2001/core";
XNamespace w = "http://schemas.microsoft.com/office/word/2003/wordml";
As we can see an XNamespace consists of an Xml Namespace URI. XLinq
has implicit conversion operators for the XNamespace and XName classes
which allows strings to be automagically converted to the classes,
very nice. After the namespaces have been defined its simply a matter
of prefixing our local names (XName) with the namespace when issuing
our queries.
var commentNodes =
from annos in wordDoc.Descendants(aml + "annotation")
where (string)annos.Attribute(w + "type") == "Word.Comment"
select annos;
Several features discussed in this post are minor API usability
features, but, when they're all taken as a whole they result in a much
more usable and productive environment for working with Xml.