void Add (ErrorType type, Node node, string message, params object[] args)
			{
				errors.Add (new Error (type, node.Location.BeginLine, node.Location.BeginColumn, string.Format (message, args)));
			}
		public void Parse (string fileName, TextReader textStream)
		{
			var parser = new AspParser (fileName, textStream);
			this.fileName = fileName;
			
			parser.Error      += ParseError;
			parser.TagParsed  += TagParsed;
			parser.TextParsed += TextParsed;
			
			currentNode = this;
			
			parser.Parse ();
		}
		void TagParsed (ILocation location, TagType tagtype, string tagId, TagAttributes attributes)
		{
			switch (tagtype)
			{
			case TagType.Close:
				TagNode tn = currentNode as TagNode;
				if (tn == null) {
					errors.Add (new ParseException (location, "Closing tag '" + tagId +"' does not match an opening tag."));
				} else {
					if (tn.TagName == tagId) {
						tn.EndLocation = location;
						currentNode = currentNode.Parent;
						tn.Close ();
					} else {
						errors.Add (new ParseException (location, "Closing tag '" + tagId +"' does not match opening tag '" + tn.TagName + "'."));
						currentNode = currentNode.Parent;
						TagParsed (location, TagType.Close, tagId, null);
					}
				}
				break;
				
			case TagType.CodeRender:
			case TagType.CodeRenderExpression:
			case TagType.DataBinding:
				try {
					AddtoCurrent (location, new ExpressionNode (location, tagId, tagtype == TagType.CodeRenderExpression));
				} catch (ParseException ex) {
					errors.Add (ex);
				}
				break;
				
			case TagType.Directive:
				try {
					AddtoCurrent (location, new DirectiveNode (location, tagId, attributes));
				} catch (ParseException ex) {
					errors.Add (ex);
				}	
				break;
				
			case TagType.Include:
				throw new NotImplementedException ("Server-side includes have not yet been implemented: " + location.PlainText);
				
			case TagType.ServerComment:
				//FIXME: the parser doesn't actually return these
				throw new NotImplementedException ("Server comments have not yet been implemented: " + location.PlainText);
				
			case TagType.SelfClosing:
				try {
					tn = new TagNode (location, tagId, attributes);
					AddtoCurrent (location, tn);
					tn.Close ();
				} catch (ParseException ex) {
					errors.Add (ex);
				}
				break;
				
			case TagType.Tag:
				try {
					//HACK: implicit close on block level in HTML4
					TagNode prevTag = currentNode as TagNode;
					if (prevTag != null) {
						if (Array.IndexOf (implicitCloseOnBlock, prevTag.TagName.ToLowerInvariant ()) > -1
						    && Array.IndexOf (blockLevel, tagId.ToLowerInvariant ()) > -1) {
							errors.Add (new ParseException (location, "Unclosed " + prevTag.TagName + " tag. Assuming implicitly closed by block level tag."));
							prevTag.Close ();
							currentNode = currentNode.Parent;
						}
					}
					
					//create and add the new tag
					TagNode child = new TagNode (location, tagId, attributes);
					AddtoCurrent (location, child);
					
					//HACK: implicitly closing tags in HTML4
					if (Array.IndexOf (implicitSelfClosing, tagId.ToLowerInvariant ()) > -1) {
						errors.Add (new ParseException (location, "Unclosed " + tagId + " tag. Assuming implicitly closed."));
						child.Close ();
					} else {
						currentNode = child;
					}
					
				} catch (ParseException ex) {
					errors.Add (ex);
				}
				break;
				
			case TagType.Text:
				//FIXME: the parser doesn't actually return these
				throw new NotImplementedException("Text tagtypes have not yet been implemented: " + location.PlainText);
			}
		}
		void AddtoCurrent (ILocation location, Node n)
		{
			ParentNode pn = currentNode as ParentNode;
			if (pn == null)
				throw new ParseException (location, "Nodes of type " + n.GetType () + " must be inside other tags");
			pn.AddChild (n);
		}