public DekiScriptExpression Visit(DekiScriptAccess expr, DekiScriptExpressionEvaluationState state) { DekiScriptExpression prefix = expr.Prefix.VisitWith(this, state); DekiScriptExpression index = expr.Index.VisitWith(this, state); DekiScriptAccess access = (DekiScriptAccess)DekiScriptExpression.Access(expr.Location, prefix, index); if ((prefix is DekiScriptLiteral) && (index is DekiScriptLiteral)) { DekiScriptLiteral result = DekiScriptExpressionEvaluation.Instance.Evaluate(access, state, false); // check if result is a property if (DekiScriptRuntime.IsProperty(result)) { // retrieve property information var descriptor = state.Runtime.ResolveRegisteredFunctionUri(((DekiScriptUri)result).Value); if ((descriptor != null) && descriptor.IsIdempotent) { // evaluate property, since it never changes return(state.Runtime.EvaluateProperty(expr.Location, result, state.Env)); } return(DekiScriptExpression.Call(expr.Location, result, new DekiScriptList())); } return(result); } return(access); }
public static Hashtable ListGroupBy( [DekiScriptParam("list value")] ArrayList list, [DekiScriptParam("expression to apply for grouping (use '$' to refer to the current item)")] string expression, DekiScriptRuntime runtime ) { Hashtable result = new Hashtable(StringComparer.OrdinalIgnoreCase); DekiScriptExpression expr = DekiScriptParser.Parse(new Location("list.groupby(expression)"), expression); // loop over all items in list foreach (object entry in list) { DekiScriptEnv env = runtime.CreateEnv(); env.Vars.Add(DekiScriptRuntime.DEFAULT_ID, DekiScriptLiteral.FromNativeValue(entry)); // evalute grouping expression string key = runtime.Evaluate(expr, DekiScriptEvalMode.EvaluateSafeMode, env).AsString(); if (key != null) { // check if an accumulation list already exists; otherwise, create one ArrayList accumulator = (ArrayList)result[key]; if (accumulator == null) { accumulator = new ArrayList(); result[key] = accumulator; } accumulator.Add(entry); } } return(result); }
public DekiScriptExpression Visit(DekiScriptAssign expr, DekiScriptExpressionEvaluationState state) { DekiScriptExpression result = expr.Value.VisitWith(this, state); DekiScriptLiteral value = (result is DekiScriptLiteral) ? (DekiScriptLiteral)result : DekiScriptUnknown.Value; if (expr.Define) { state.Env.Vars.Add(expr.Variable, value); } else { state.Env.Vars[expr.Variable] = value; } if (result is DekiScriptLiteral) { // NOTE: variable was assigned a value; it will be substituted into subsequent operations return(DekiScriptNil.Value); } if (expr.Define) { return(DekiScriptExpression.VarStatement(expr.Location, DekiScriptExpression.Id(expr.Location, expr.Variable), result)); } return(DekiScriptExpression.LetStatement(expr.Location, DekiScriptExpression.Id(expr.Location, expr.Variable), result)); }
private DekiScriptLiteral InvokeHelper(DekiScriptRuntime runtime, DekiScriptList args) { // convert passed in arguments object[] arguments = new object[Parameters.Length]; int i = 0; try { for (; i < Parameters.Length; ++i) { var value = args[i].NativeValue; // check if we need to convert the value if ((value != null) && (Parameters[i].NativeType != typeof(object))) { // check for the special case where we cast from XML to STR if ((value is XDoc) && (Parameters[i].NativeType == typeof(string))) { XDoc xml = (XDoc)value; if (xml.HasName("html")) { value = xml["body[not(@target)]"].Contents; } else { value = xml.ToString(); } } else { // rely on the default type conversion rules value = SysUtil.ChangeType(value, Parameters[i].NativeType); } } arguments[i] = value; } } catch { throw new ArgumentException(string.Format("could not convert parameter '{0}' (index {1}) from {2} to {3}", Parameters[i].Name, i, args[i].ScriptTypeName, DekiScriptLiteral.AsScriptTypeName(Parameters[i].ScriptType))); } // invoke method var result = _invoke(runtime, arguments); // check if result is a URI if (result is XUri) { // normalize URI if possible DreamContext context = DreamContext.CurrentOrNull; if (context != null) { result = context.AsPublicUri((XUri)result); } } var literal = DekiScriptLiteral.FromNativeValue(result); try { return(literal.Convert(ReturnType)); } catch (DekiScriptInvalidCastException) { throw new DekiScriptInvalidReturnCastException(Location.None, literal.ScriptType, ReturnType); } }
internal void ReplaceNodeWithValue(XmlNode node, DekiScriptLiteral value) { XmlNode parent = node.ParentNode; InsertValueBeforeNode(parent, node, value); parent.RemoveChild(node); }
public static Hashtable MapFilter( [DekiScriptParam("map value")] Hashtable map, [DekiScriptParam("condition to execute for each item (use '$' to refer to the key-value pair)")] string condition, DekiScriptRuntime runtime ) { DekiScriptExpression expr = DekiScriptParser.Parse(new Location("map.select(condition)"), condition); Hashtable result = new Hashtable(StringComparer.OrdinalIgnoreCase); foreach (DictionaryEntry entry in map) { DekiScriptMap keyvalue = new DekiScriptMap(); object value = Eval(entry.Value, runtime); keyvalue.Add("key", DekiScriptLiteral.FromNativeValue(entry.Key)); keyvalue.Add("value", DekiScriptLiteral.FromNativeValue(value)); DekiScriptEnv env = runtime.CreateEnv(); env.Vars.Add(DekiScriptRuntime.DEFAULT_ID, keyvalue); DekiScriptLiteral test = runtime.Evaluate(expr, DekiScriptEvalMode.EvaluateSafeMode, env); if (!test.IsNilFalseZero) { result.Add(entry.Key, value); } } return(result); }
public Hashtable PopularPages( [DekiExtParam("max results (default: 10)", true)] int?max, [DekiExtParam("poll interval (only for js format, default: 30)", true)] int?interval ) { int maxResults = max ?? 10; int resultCount = 0; DekiScriptMap env = DreamContext.Current.GetState <DekiScriptMap>(); DekiScriptLiteral uriLiteral = env.GetAt("site.uri"); XUri deki = new XUri(uriLiteral.NativeValue.ToString()).At("@api", "deki"); Hashtable map = new Hashtable(StringComparer.OrdinalIgnoreCase); map.Add("interval", _ttl.TotalSeconds); ArrayList pages = new ArrayList(); map.Add("pages", pages); int total = 0; Dictionary <uint, int> rankLookup = new Dictionary <uint, int>(); lock (_pageViews) { foreach (View view in _pageViews) { if (rankLookup.ContainsKey(view.PageId)) { rankLookup[view.PageId]++; } else { rankLookup[view.PageId] = 1; } total++; } } List <Tuplet <uint, int> > rank = new List <Tuplet <uint, int> >(); foreach (KeyValuePair <uint, int> kvp in rankLookup) { rank.Add(new Tuplet <uint, int>(kvp.Key, kvp.Value)); } rank.Sort(delegate(Tuplet <uint, int> a, Tuplet <uint, int> b) { return(b.Item2.CompareTo(a.Item2)); }); map.Add("total", total); foreach (Tuplet <uint, int> page in rank) { Hashtable pageMap = new Hashtable(StringComparer.OrdinalIgnoreCase); pages.Add(pageMap); // BUGBUGBUG (arnec): the AsLocalUri should not be required after bug #5964 is resolved pageMap.Add("page", DekiScriptExpression.Constant(deki.At("$page").AsLocalUri(), new[] { DekiScriptExpression.Constant(page.Item1), DekiScriptExpression.Constant(true) })); pageMap.Add("views", page.Item2); resultCount++; if (resultCount >= maxResults) { break; } } return(map); }
//--- Class Methods --- public static bool IsProperty(DekiScriptLiteral value) { if (value.ScriptType == DekiScriptType.URI) { return((((DekiScriptUri)value).Value.LastSegment ?? string.Empty).StartsWithInvariant("$")); } return(false); }
//--- Constructors --- public DekiScriptReturnException(Location location, DekiScriptLiteral value) : base(location, "Unhandled 'return' statement") { if (value == null) { throw new ArgumentNullException("value"); } this.Value = value; }
//--- Methods --- public DekiScriptLiteral Convert(DekiScriptLiteral value) { try { return(value.Convert(ScriptType)); } catch (DekiScriptInvalidCastException) { throw new DekiScriptInvalidParameterCastException(Location.None, this, value.ScriptType); } }
//--- Methods --- public int Compare(object left, object right) { _values.Add("left", DekiScriptLiteral.FromNativeValue(left)); _values.Add("right", DekiScriptLiteral.FromNativeValue(right)); _env.Vars.Add(DekiScriptRuntime.DEFAULT_ID, _values); DekiScriptLiteral test = _runtime.Evaluate(_compare, DekiScriptEvalMode.EvaluateSafeMode, _env); return((int)(test.AsNumber() ?? 0.0)); }
public static ArrayList ListOrderBy( [DekiScriptParam("list value")] ArrayList list, [DekiScriptParam("key name or list of key names; sort direction is controlled by appendending \" ascending\" or \" descending\" to the key(s); when omitted, the direction is asending by default")] object keys, DekiScriptRuntime runtime ) { // check for trivial sort case if (keys is string) { string key = ((string)keys).Trim(); // the key cannot contain access operators if (key.IndexOfAny(new[] { '.', '[', ']' }) < 0) { return(ListOrderBy_IsReversed(ref key) ? ListSort(list, key, true, null, runtime) : ListSort(list, key, false, null, runtime)); } // let's change 'keys' into an array list for processing convenience ArrayList temp = new ArrayList { keys }; keys = temp; } // check that 'keys' has a valid type if (!(keys is ArrayList)) { throw new DekiScriptBadTypeException(Location.None, DekiScriptLiteral.AsScriptType(keys.GetType()), new[] { DekiScriptType.STR, DekiScriptType.LIST }); } // build comparison expression StringBuilder compare = new StringBuilder(); foreach (string key in (ArrayList)keys) { if (compare.Length > 0) { compare.Append(" || "); } // process sort field string field = key; if (ListOrderBy_IsReversed(ref field)) { compare.AppendFormat("(if($left.{0} < $right.{0}) 1; else if($left.{0} > $right.{0}) -1; else 0;)", field); } else { compare.AppendFormat("(if($left.{0} < $right.{0}) -1; else if($left.{0} > $right.{0}) 1; else 0;)", field); } } // sort list list.Sort(new DekiScriptComparer(runtime, DekiScriptParser.Parse(new Location("list.orderby(compare)"), compare.ToString()))); return(list); }
public Empty Visit(DekiScriptGeneratorIf expr, DekiScriptGeneratorEvaluationState state) { DekiScriptLiteral condition = expr.Condition.VisitWith(DekiScriptExpressionEvaluation.Instance, state.Env); if (!condition.IsNilFalseZero) { Generate(expr, state); } return(Empty.Value); }
public Empty Visit(DekiScriptGeneratorForeachValue expr, DekiScriptGeneratorEvaluationState state) { DekiScriptLiteral collection = expr.Collection.VisitWith(DekiScriptExpressionEvaluation.Instance, state.Env); // retrieve collection List <DekiScriptLiteral> list; if (collection is DekiScriptList) { // loop over list values list = ((DekiScriptList)collection).Value; } else if (collection is DekiScriptMap) { // loop over map key-value pairs list = new List <DekiScriptLiteral>(((DekiScriptMap)collection).Value.Values); } else if (collection is DekiScriptXml) { // loop over xml selection List <XDoc> selection = ((DekiScriptXml)collection).Value.ToList(); list = new List <DekiScriptLiteral>(selection.Count); foreach (XDoc doc in selection) { list.Add(new DekiScriptXml(doc)); } } else if (collection is DekiScriptNil) { // nothing to do list = new List <DekiScriptLiteral>(); } else { throw new DekiScriptBadTypeException(expr.Line, expr.Column, collection.ScriptType, new DekiScriptType[] { DekiScriptType.LIST, DekiScriptType.MAP, DekiScriptType.XML, DekiScriptType.NIL }); } // loop over collection int index = 0; for (int i = 0; i <= (list.Count - expr.Vars.Length); i += expr.Vars.Length) { // set the environment variable for (int j = 0; j < expr.Vars.Length; ++j) { state.Env.Locals.Add(expr.Vars[j], list[i + j]); } state.Env.Locals.Add(DekiScriptRuntime.INDEX_ID, DekiScriptNumber.New(index)); // iterate over block statements Generate(expr, state); index += expr.Vars.Length; } return(Empty.Value); }
private static object Eval(object value, DekiScriptRuntime runtime) { if (value is XUri) { DekiScriptLiteral uri = DekiScriptExpression.Constant((XUri)value); if (DekiScriptRuntime.IsProperty(uri)) { value = runtime.EvaluateProperty(Location.None, uri, runtime.CreateEnv()).NativeValue; } } return(value); }
//--- Extension Methods --- public static StringBuilder AppendLiteral(this StringBuilder builder, DekiScriptLiteral literal) { if (literal is DekiScriptString) { builder.Append(((DekiScriptString)literal).Value); } else { builder.Append(literal.ToString()); } return(builder); }
public DekiScriptOutputBuffer.Range Visit(DekiScriptSwitch expr, DekiScriptExpressionEvaluationState state) { DekiScriptLiteral value = state.Pop(expr.Value.VisitWith(this, state)); DekiScriptSwitch.CaseBlock caseBlock = null; // have to use for instead of foreach, since a fallthrough default case needs to be able to look ahead for (int i = 0; i < expr.Cases.Length; i++) { DekiScriptSwitch.CaseBlock current = expr.Cases[i]; // check for default case foreach (DekiScriptExpression condition in current.Conditions) { if (condition == null) { // check if this is the first default we've found if (caseBlock == null) { caseBlock = current; } // continue in case loop, since default only gets executed if there is no match continue; } // evaluate test DekiScriptExpression test = DekiScriptExpression.BinaryOp(current.Location, DekiScriptBinary.Op.Equal, value, condition); DekiScriptLiteral caseMatch = state.Pop(test.VisitWith(this, state)); // evaluate body on success if (!caseMatch.IsNilFalseZero) { // found a matching cast statement caseBlock = current; break; } } } // haven't found a match yet, so if we have a default, return it if (caseBlock != null) { int marker = state.Buffer.Marker; try { return(caseBlock.Body.VisitWith(this, state)); } catch (DekiScriptBreakException) { // nothing to do } return(state.Buffer.Since(marker)); } return(DekiScriptOutputBuffer.Range.Empty); }
//--- Methods --- public Empty Visit(DekiScriptGeneratorIf expr, DekiScriptGeneratorEvaluationState state) { // evaluate the generator condition DekiScriptLiteral condition = Eval(expr.Condition, state); // check if condition is met if (!condition.IsNilFalseZero) { EvalNext(expr, state); } return(Empty.Value); }
public object Fetch( [DekiExtParam("document id")] string id ) { // fetch response from cache CacheEntry result; lock (_cacheLookup) { _cacheLookup.TryGetValue(id, out result); } // check if we have a cached entry XDoc document = null; if (result != null) { _log.DebugFormat("cache hit for '{0}'", result.Id); result.ResetMemoryExpiration(); if (result.Cache != null) { _log.DebugFormat("cache data in memory '{0}'", result.Id); document = XDocFactory.From(result.Cache, MimeType.XML); } else { // we have the result on disk, so let's fetch it DreamMessage msg = Storage.At(CACHE_DATA, result.Guid + ".bin").GetAsync().Wait(); if (msg.IsSuccessful) { _log.DebugFormat("cache data pulled from disk"); result.Cache = Encoding.UTF8.GetString(msg.AsBytes()); document = XDocFactory.From(result.Cache, MimeType.XML); } else { _log.DebugFormat("unable to fetch cache data from disk: {0}", msg.Status); } } } // check if we have a document to convert if (document != null) { try { DekiScriptList list = (DekiScriptList)DekiScriptLiteral.FromXml(document); return(list[0]); } catch { // the cached entry is corrupted, remove it Clear(id); } } return(null); }
//--- Contructors --- public DekiScriptParameter(string name, DekiScriptType type, bool optional, string hint) { if (string.IsNullOrEmpty(name)) { throw new NullReferenceException("name"); } this.Name = name; this.ScriptType = type; this.Optional = optional; this.Hint = hint; this.NativeType = typeof(object); this.Default = DekiScriptNil.Value; }
public Empty Visit(DekiScriptDomExpr expr, DekiScriptDomEvaluationState state) { var context = state.Context; var env = state.Env; var parent = state.Parent; try { DekiScriptLiteral value = expr.Value.VisitWith(DekiScriptExpressionEvaluation.Instance, env.NewLocalScope()); context.InsertValueBeforeNode(parent, null, value); } catch (Exception e) { EmbedExceptionMessage(expr, env, context, e, parent); } return(Empty.Value); }
public Range Push(DekiScriptLiteral literal) { if (literal.IsNil) { return(Range.Empty); } // append element int start = _buffer.Count; CheckBufferLimits(); _buffer.Add(literal); return(new Range(start, _buffer.Count)); }
public void AppendXml(XDoc doc) { doc.Start("param").Attr("name", Name).Attr("type", DekiScriptLiteral.AsScriptTypeName(ScriptType)); if (Default.IsNil) { doc.Attr("optional", Optional ? "true" : null); } else { doc.Attr("default", Default.ToString()); } doc.Value(Hint); doc.End(); }
public Empty Visit(DekiScriptDomJson expr, DekiScriptDomEvaluationState state) { var context = state.Context; var env = state.Env; var parent = state.Parent; try { DekiScriptLiteral value = expr.Value.VisitWith(DekiScriptExpressionEvaluation.Instance, env); XmlText result = context.CreateTextNode(DekiScriptLibrary.JsonEmit(value.NativeValue)); parent.AppendChild(result); } catch (Exception e) { EmbedExceptionMessage(expr, env, context, e, parent); } return(Empty.Value); }
public DekiScriptOutputBuffer.Range Visit(DekiScriptTernary expr, DekiScriptExpressionEvaluationState state) { DekiScriptLiteral test = state.Pop(expr.Test.VisitWith(this, state)); DekiScriptLiteral result; // check which branch should be executed if (!test.IsNilFalseZero) { return(expr.Left.VisitWith(this, state)); } else { return(expr.Right.VisitWith(this, state)); } }
//--- Methods --- public virtual DekiScriptLiteral Invoke(DekiScriptRuntime runtime, DekiScriptLiteral args) { if (args is DekiScriptList) { return(InvokeList(runtime, (DekiScriptList)args)); } else if (args is DekiScriptMap) { return(InvokeMap(runtime, (DekiScriptMap)args)); } else { throw new DekiScriptBadTypeException(Location.None, args.ScriptType, new[] { DekiScriptType.MAP, DekiScriptType.LIST }); } }
public Empty Visit(DekiScriptGeneratorForeachKeyValue expr, DekiScriptGeneratorEvaluationState state) { DekiScriptLiteral collection = Eval(expr.Collection, state); // retrieve collection Dictionary <string, DekiScriptLiteral> map; if (collection is DekiScriptMap) { // loop over map key-value pairs map = ((DekiScriptMap)collection).Value; } else if (collection is DekiScriptNil) { // nothing to do map = new Dictionary <string, DekiScriptLiteral>(); } else { throw new DekiScriptBadTypeException(expr.Location, collection.ScriptType, new[] { DekiScriptType.MAP, DekiScriptType.NIL }); } // store state of variables var previousKey = state.State.Env.Vars[expr.Key]; var previousValue = state.State.Env.Vars[expr.Value]; var previousIndex = state.State.Env.Vars[DekiScriptRuntime.INDEX_ID]; try { // loop over collection int index = 0; foreach (KeyValuePair <string, DekiScriptLiteral> entry in map) { // set the environment variable state.State.Env.Vars.Add(expr.Key, DekiScriptExpression.Constant(entry.Key)); state.State.Env.Vars.Add(expr.Value, entry.Value); state.State.Env.Vars.Add(DekiScriptRuntime.INDEX_ID, DekiScriptExpression.Constant(index)); // iterate over block statements EvalNext(expr, state); ++index; } } finally { state.State.Env.Vars.Add(expr.Key, previousKey); state.State.Env.Vars.Add(expr.Value, previousValue); state.State.Env.Vars.Add(DekiScriptRuntime.INDEX_ID, previousIndex); } return(Empty.Value); }
public T EnvAt <T>(string path) { if (path == null) { throw new ArgumentNullException("path"); } DekiScriptMap env = DreamContext.Current.GetState <DekiScriptMap>(); if (env == null) { return(default(T)); } DekiScriptLiteral value = env.GetAt(path); return(SysUtil.ChangeType <T>(value.NativeValue)); }
public Yield Execute(DreamContext context, DreamMessage request, Result <DreamMessage> response) { string expression = context.GetParam("expression"); DekiScriptExpression expr = DekiScriptParser.Parse(new Location("POST:execute"), expression); DekiScriptLiteral result = _runtime.Evaluate(expr, DekiScriptEvalMode.Evaluate, _env); if (result.ScriptType == DekiScriptType.XML) { response.Return(DreamMessage.Ok(MimeType.XML, (XDoc)result.NativeValue)); } else { response.Return(DreamMessage.Ok(MimeType.TEXT, result.ToString())); } yield break; }
public virtual DekiScriptLiteral EvaluateProperty(Location location, DekiScriptLiteral value, DekiScriptEnv env) { if (IsProperty(value)) { DekiScriptUri uri = (DekiScriptUri)value; try { value = Invoke(location, uri.Value, uri.Arguments.IsNil ? _emptyList : uri.Arguments, env); } catch (DekiScriptFatalException) { throw; } catch (Exception e) { var descriptor = ResolveRegisteredFunctionUri(uri.Value); throw new DekiScriptInvokeException(location, uri.Value, (descriptor != null) ? descriptor.Name : uri.Value.ToString(), e); } } return(value); }
public DekiScriptFlowControlException(DekiScriptFlowControl flowControl, DekiScriptLiteral accumulatedState) : base(0, 0, string.Format("Unhandled flow control statement '{0}'", flowControl.ToString().ToLower())) { FlowControl = flowControl; AccumulatedState = accumulatedState; }
//--- Methods --- public DekiScriptEvaluationAccumulator Add(DekiScriptLiteral literal, bool safe) { if(literal == null) { throw new ArgumentNullException("literal"); } if(literal is DekiScriptNil) { // nothing to do return this; } // check if any value was accumulated if(_value == null) { if(literal is DekiScriptXml) { _value = ((DekiScriptXml)literal).Value.Clone(); } else { _value = literal; } return this; } // check if we can append a string value if(literal is DekiScriptString) { AddString(((DekiScriptString)literal).Value, safe); return this; } if(!(literal is DekiScriptUri) && !(literal is DekiScriptXml)) { AddString(literal.ToString(), safe); return this; } // check if we need to append an XML document XDoc doc = literal.AsEmbeddableXml(safe); if(doc.IsEmpty) { return this; } XDoc accumulator = ConvertToXml(safe); // build lookup for existing bodies in accumulator Dictionary<string, XDoc> bodies = new Dictionary<string, XDoc>(); foreach(XmlNode node in accumulator.AsXmlNode.ChildNodes) { if(node.NodeType == XmlNodeType.Element) { XmlElement element = (XmlElement)node; if(StringUtil.EqualsInvariant(node.LocalName, "body")) { string target = element.GetAttribute("target"); bodies[target] = accumulator[node]; } } } // loop over all root children in new document foreach(XmlNode node in doc.AsXmlNode.ChildNodes) { if(node.NodeType == XmlNodeType.Element) { XmlElement element = (XmlElement)node; if(StringUtil.EqualsInvariant(node.LocalName, "body")) { string target = element.GetAttribute("target"); XDoc body; if(bodies.TryGetValue(target, out body)) { // body already exists, check how it should be handled string conflict = element.GetAttribute("conflict"); if(string.IsNullOrEmpty(conflict)) { // default conflict resolution depends on target: no target (i.e. main body) is append, otherwise it is ignore conflict = string.IsNullOrEmpty(target) ? "append" : "ignore"; } switch(conflict) { case "replace": // replace existing body with new one body.RemoveNodes(); body.AddNodes(doc[node]); break; case "append": // append nodes to existing body body.AddNodes(doc[node]); break; case "ignore": // ignore new body break; } } else { // target body does not exist, append it accumulator.Start("body"); if(!string.IsNullOrEmpty(target)) { accumulator.Attr("target", target); } accumulator.AddNodes(doc[node]); accumulator.End(); } } else if(StringUtil.EqualsInvariant(node.LocalName, "head")) { XDoc head = accumulator["head"]; foreach(XmlNode child in node.ChildNodes) { head.Add(doc[child]); } } else if(StringUtil.EqualsInvariant(node.LocalName, "tail")) { XDoc head = accumulator["tail"]; foreach(XmlNode child in node.ChildNodes) { head.Add(doc[child]); } } } } return this; }