/// <summary> /// Attaches an element named ShippingNotRequired that serves as a flag whether our order does not require shipping checking all the available factors /// </summary> /// <param name="nav">The XPathNavigator</param> private void CheckIfWeShouldRequireShipping(XPathNavigator nav) { XmlNode orderInfoNode = GetXmlNode(nav.SelectSingleNode("Order/OrderInfo")); bool weDontRequireShipping = false; if (AppLogic.AppConfigBool("SkipShippingOnCheckout") == true) { weDontRequireShipping = true; } else { bool isMultiShipping = XmlCommon.XmlFieldBool(orderInfoNode, "multiship"); bool allAreDownloadProducts = XmlCommon.XmlFieldBool(orderInfoNode, "allDownloads"); bool allAreSystemProducts = XmlCommon.XmlFieldBool(orderInfoNode, "allSystemproducts"); weDontRequireShipping = isMultiShipping == false && (allAreDownloadProducts || allAreSystemProducts); if (weDontRequireShipping == false) { // now check if all of the line items is No Shipping Required.. XPathNodeIterator lineItemsThatAreNotFreeShippingIfAnyNodeIterator = nav.Select("OrderItems/Item[FreeShipping != 2]"); weDontRequireShipping = !lineItemsThatAreNotFreeShippingIfAnyNodeIterator.MoveNext(); } } XmlNode shippingNotRequiredNode = orderInfoNode.OwnerDocument.CreateNode(XmlNodeType.Element, "ShippingNotRequired", string.Empty); shippingNotRequiredNode.InnerText = XmlCommon.XmlEncode(weDontRequireShipping.ToString()); orderInfoNode.InsertAfter(shippingNotRequiredNode, orderInfoNode.LastChild); }
/// <summary> /// Check if order is free shipping and customer selects free shipping methods /// </summary> /// <param name="nav">xml</param> private void CheckIfFreeShipping(XPathNavigator nav) { XmlNode orderInfoNode = GetXmlNode(nav.SelectSingleNode("Order/OrderInfo")); bool freeShipping = false; if ((CheckIfCouponApplied(orderInfoNode) || CheckIfLevelHasFreeShipping(orderInfoNode)) || (CheckIfAllDownloads(orderInfoNode) || CheckIfAllFreeShipping(orderInfoNode) || CheckIfAllSystemProducts(orderInfoNode))) { freeShipping = true; } bool customerChoseFreeShippingMethod = true; if (AppLogic.AppConfigBool("FreeShippingAllowsRateSelection")) { int shippingMethodId = XmlCommon.XmlFieldNativeInt(orderInfoNode, "ShippingMethodID"); string commaSeparatedIds = AppLogic.AppConfig("ShippingMethodIDIfFreeShippingIsOn"); customerChoseFreeShippingMethod = CommonLogic.IntegerIsInIntegerList(shippingMethodId, commaSeparatedIds); } freeShipping = freeShipping && customerChoseFreeShippingMethod; XmlNode isFreeShippingNode = orderInfoNode.OwnerDocument.CreateNode(XmlNodeType.Element, "IsFreeShipping", string.Empty); isFreeShippingNode.InnerText = XmlCommon.XmlEncode(freeShipping.ToString()); orderInfoNode.InsertAfter(isFreeShippingNode, orderInfoNode.LastChild); }
private static string StringResourceMatch(Match match) { String l = match.Groups[1].Value; string s = HttpUtility.HtmlEncode(AppLogic.GetString(l, DEFAULT_SKINID, Customer.Current.LocaleSetting)); if (s == null || s.Length == 0 || s == l) { s = match.Value; } return(XmlCommon.XmlEncode(s)); }
// for paymentech gateway, which is kind of silly: static public String XmlEncodeMaxLength(String s, int MaxChars) { String result = String.Empty; foreach (char c in s) { String sx = new String(c, 1); sx = XmlCommon.XmlEncode(sx); if (result.Length + sx.Length < MaxChars) { result += sx; } } return(result); }
/// <summary> /// Attaches the Order Options text as Xml element contained in the OrderInfo Node /// </summary> /// <param name="nav">The XPathNavigator</param> private void AttachOrderOptionsXml(XPathNavigator nav) { XmlNode orderInfoNode = GetXmlNode(nav.SelectSingleNode("Order/OrderInfo")); CultureInfo culture = new CultureInfo(ThisCustomer.LocaleSetting); try { XmlNode orderOptionsNode = orderInfoNode.SelectSingleNode("OrderOptions"); if (null == orderOptionsNode) { return; } XmlDocument doc = orderInfoNode.OwnerDocument; string orderOptions = orderOptionsNode.InnerText; XmlNode orderOptionsXml = doc.CreateNode(XmlNodeType.Element, "OrderOptionsXml", string.Empty); if (!string.IsNullOrEmpty(orderOptions)) { string[] orderOptionDelimitedValues = orderOptions.Split('^'); foreach (string orderOptionDelimitedValue in orderOptionDelimitedValues) { string[] orderOptionValues = orderOptionDelimitedValue.Split('|'); if (orderOptionValues != null && orderOptionValues.Length > 0) { int id = int.Parse(orderOptionValues[0]); Guid uniqueID = new Guid(orderOptionValues[1]); string name = orderOptionValues[2]; string priceFormatted = orderOptionValues[3]; // NOTE: // Since the order options are attached to the order as a | delimited string // and the price and tax amounts are already hardcoded as strings together // with their currency symbols, we need to extract only the numeric values decimal price = ParseAmount(priceFormatted, culture); // since the order option is saved as one whole string // the price saved here is already converted into the target curency format // we'll need to revert to the original currency setting so to display properly especially on different currencies price = Currency.Convert(price, ThisCustomer.CurrencySetting, Localization.GetPrimaryCurrency()); string extPriceFormatted = priceFormatted; bool withVat = orderOptionValues.Length >= 4; string vatFormatted = string.Empty; decimal vat = decimal.Zero; if (withVat) { vatFormatted = orderOptionValues[4]; vat = ParseAmount(vatFormatted, culture); } XmlNode orderOptionNode = doc.CreateNode(XmlNodeType.Element, "OrderOption", string.Empty); // the details XmlNode idNode = doc.CreateNode(XmlNodeType.Element, "ID", string.Empty); XmlNode nameNode = doc.CreateNode(XmlNodeType.Element, "ProductName", string.Empty); XmlNode priceNode = doc.CreateNode(XmlNodeType.Element, "Price", string.Empty); XmlNode vatNode = doc.CreateNode(XmlNodeType.Element, "VAT", string.Empty); XmlNode imageUrlNode = doc.CreateNode(XmlNodeType.Element, "ImageUrl", string.Empty); idNode.InnerText = XmlCommon.XmlEncode(id.ToString()); nameNode.InnerXml = XmlCommon.XmlEncode(name); // NOTE: this value may be localized, make sure to call GetMLValue on the xml package!!! priceNode.InnerText = XmlCommon.XmlEncode(price.ToString()); vatNode.InnerText = XmlCommon.XmlEncode(vat.ToString()); // get the image info string imgUrl = orderOptionValues[5]; if (!string.IsNullOrEmpty(CommonLogic.ServerVariables("HTTP_HOST"))) { imgUrl = "http://" + CommonLogic.ServerVariables("HTTP_HOST") + imgUrl; } imageUrlNode.InnerText = XmlCommon.XmlEncode(imgUrl); orderOptionNode.AppendChild(idNode); orderOptionNode.AppendChild(nameNode); orderOptionNode.AppendChild(priceNode); orderOptionNode.AppendChild(vatNode); orderOptionNode.AppendChild(imageUrlNode); orderOptionsXml.AppendChild(orderOptionNode); } } } orderInfoNode.InsertAfter(orderOptionsXml, orderInfoNode.LastChild); } catch { } }
/// <summary> /// Precompute the line item vat and discounts and attach it inside the order info node /// </summary> /// <param name="nav">The XPathNavigator</param> private void PreComputeLineItemIntrinsics(XPathNavigator nav) { XmlNode orderInfoNode = GetXmlNode(nav.SelectSingleNode("Order/OrderInfo")); XPathNodeIterator lineItemNodeIterator = nav.Select("OrderItems/Item"); CultureInfo cultureInfo; var orderCultureInfo = CommonLogic.Application("DBSQLServerLocaleSetting"); cultureInfo = string.IsNullOrEmpty(orderCultureInfo) ? CultureInfo.InvariantCulture : new CultureInfo(orderCultureInfo); decimal allLineItemDiscounts = decimal.Zero; while (lineItemNodeIterator.MoveNext()) { XmlNode lineItemNode = GetXmlNode(lineItemNodeIterator.Current); bool isAKit = XmlCommon.XmlFieldBool(lineItemNode, "IsAKit"); int quantity = 1; int.TryParse(XmlCommon.XmlField(lineItemNode, "Quantity"), out quantity); decimal price = 0; Decimal.TryParse(XmlCommon.XmlField(lineItemNode, "OrderedProductRegularPrice"), NumberStyles.AllowDecimalPoint, cultureInfo, out price); decimal orderedExtendedPrice = 0; Decimal.TryParse(XmlCommon.XmlField(lineItemNode, "OrderedProductPrice"), NumberStyles.AllowDecimalPoint, cultureInfo, out orderedExtendedPrice); decimal taxRate = Decimal.Parse(XmlCommon.XmlField(lineItemNode, "TaxRate"), NumberStyles.AllowDecimalPoint, cultureInfo); decimal vatAmount = decimal.Zero; decimal extendedVatAmount = decimal.Zero; bool applyVat = AppLogic.AppConfigBool("VAT.Enabled") == true && XmlCommon.XmlFieldBool(orderInfoNode, "LevelHasNoTax") == false && XmlCommon.XmlFieldBool(lineItemNode, "IsTaxable") == true && string.IsNullOrEmpty(XmlCommon.XmlField(orderInfoNode, "VATRegistrationID")); if (applyVat) { if (AppLogic.AppConfigBool("VAT.RoundPerItem")) { vatAmount = (((price / quantity) * taxRate) / 100M) * quantity; extendedVatAmount = (((orderedExtendedPrice / quantity) * taxRate) / 100M) * quantity; } else { vatAmount = (price * taxRate) / 100M; extendedVatAmount = (orderedExtendedPrice * taxRate) / 100M; } if (isAKit) { vatAmount = extendedVatAmount / quantity; } } // let's save these as decimal values, leave out formatting on a later call XmlNode vatAmountNode = lineItemNode.OwnerDocument.CreateNode(XmlNodeType.Element, "VatAmount", string.Empty); vatAmountNode.InnerText = XmlCommon.XmlEncode(vatAmount.ToString(cultureInfo)); XmlNode extVatAmountNode = lineItemNode.OwnerDocument.CreateNode(XmlNodeType.Element, "ExtVatAmount", string.Empty); extVatAmountNode.InnerText = XmlCommon.XmlEncode(extendedVatAmount.ToString(cultureInfo)); // insert these nodes on the bottom lineItemNode.InsertAfter(vatAmountNode, lineItemNode.LastChild); lineItemNode.InsertAfter(extVatAmountNode, lineItemNode.LastChild); decimal regularExtendedPrice = decimal.Zero; decimal discount = decimal.Zero; // kit products don't save the regular price only the sales price // we won't be supporting line item discounts on these item types then if (isAKit) { regularExtendedPrice = decimal.Zero; discount = decimal.Zero; price = orderedExtendedPrice / quantity; } else { // make sure to round to 2 decimal places price = Math.Round(price, 2, MidpointRounding.AwayFromZero); regularExtendedPrice = price * quantity; discount = (regularExtendedPrice - orderedExtendedPrice); } bool showPriceVatInclusive = ThisCustomer.VATSettingReconciled == VATSettingEnum.ShowPricesInclusiveOfVAT; decimal displayPrice = price; if (applyVat && showPriceVatInclusive) { displayPrice += vatAmount; } decimal displayExtPrice = orderedExtendedPrice; if (applyVat && showPriceVatInclusive) { displayExtPrice += extendedVatAmount; } // let's save these as decimal values, leave out formatting on a later call XmlNode extRegularPriceNode = lineItemNode.OwnerDocument.CreateNode(XmlNodeType.Element, "ExtendedRegularPrice", string.Empty); extRegularPriceNode.InnerText = XmlCommon.XmlEncode(regularExtendedPrice.ToString(cultureInfo)); // insert these nodes on the bottom lineItemNode.InsertAfter(extRegularPriceNode, lineItemNode.LastChild); XmlNode discountNode = lineItemNode.OwnerDocument.CreateNode(XmlNodeType.Element, "DiscountAmount", string.Empty); discountNode.InnerText = XmlCommon.XmlEncode(discount.ToString(cultureInfo)); // insert these nodes on the bottom lineItemNode.InsertAfter(discountNode, lineItemNode.LastChild); // price column XmlNode priceNode = lineItemNode.OwnerDocument.CreateNode(XmlNodeType.Element, "Price", string.Empty); priceNode.InnerText = XmlCommon.XmlEncode(price.ToString(cultureInfo)); // insert these nodes on the bottom lineItemNode.InsertAfter(priceNode, lineItemNode.LastChild); XmlNode displayPriceNode = lineItemNode.OwnerDocument.CreateNode(XmlNodeType.Element, "DisplayPrice", string.Empty); displayPriceNode.InnerText = XmlCommon.XmlEncode(displayPrice.ToString(cultureInfo)); // insert these nodes on the bottom lineItemNode.InsertAfter(displayPriceNode, lineItemNode.LastChild); XmlNode displayExtPriceNode = lineItemNode.OwnerDocument.CreateNode(XmlNodeType.Element, "DisplayExtPrice", string.Empty); displayExtPriceNode.InnerText = XmlCommon.XmlEncode(displayExtPrice.ToString(cultureInfo)); // insert these nodes on the bottom lineItemNode.InsertAfter(displayExtPriceNode, lineItemNode.LastChild); XmlNode discountWithVATNode = lineItemNode.OwnerDocument.CreateNode(XmlNodeType.Element, "DiscountWithVAT", string.Empty); discountWithVATNode.InnerText = XmlCommon.XmlEncode((displayPrice - displayExtPrice).ToString(cultureInfo)); // insert these nodes on the bottom lineItemNode.InsertAfter(discountWithVATNode, lineItemNode.LastChild); // store the line item discounts allLineItemDiscounts += XmlCommon.XmlFieldNativeDecimal(lineItemNode, "DiscountAmount"); } bool hasLineItemDiscounts = false; hasLineItemDiscounts = allLineItemDiscounts > decimal.Zero; XmlNode hasLineItemDiscountsNode = orderInfoNode.OwnerDocument.CreateNode(XmlNodeType.Element, "HasLineItemDiscounts", string.Empty); hasLineItemDiscountsNode.InnerText = XmlCommon.XmlEncode(hasLineItemDiscounts.ToString(cultureInfo)); orderInfoNode.InsertAfter(hasLineItemDiscountsNode, orderInfoNode.LastChild); }
/// <summary> /// Precompute the order discounts and attach it to the OrderInfo node /// </summary> /// <param name="nav">The XPathNavigator</param> private void PreComputeDiscounts(XPathNavigator nav) { // Precompute the totals XmlNode orderInfoNode = GetXmlNode(nav.SelectSingleNode("Order/OrderInfo")); decimal rawSubTotal = decimal.Zero; decimal rawSubTotalVat = decimal.Zero; XPathNodeIterator lineItemIterator = nav.Select("OrderItems/Item"); bool incVat = ThisCustomer.VATSettingReconciled == VATSettingEnum.ShowPricesInclusiveOfVAT; while (lineItemIterator.MoveNext()) { XmlNode lineItemNode = GetXmlNode(lineItemIterator.Current); rawSubTotal += XmlCommon.XmlFieldNativeDecimal(lineItemNode, "OrderedProductPrice"); if (incVat) { rawSubTotalVat += XmlCommon.XmlFieldNativeDecimal(lineItemNode, "ExtVatAmount"); } } XPathNodeIterator orderOptionIterator = nav.Select("Order/OrderInfo/OrderOptionsXml/OrderOption"); while (orderOptionIterator.MoveNext()) { XmlNode orderOptionNode = GetXmlNode(orderOptionIterator.Current); rawSubTotal += XmlCommon.XmlFieldNativeDecimal(orderOptionNode, "Price"); } decimal appliedSubTotal = rawSubTotal; decimal subTotal = XmlCommon.XmlFieldNativeDecimal(orderInfoNode, "OrderSubtotal"); XmlNode rawSubTotalNode = orderInfoNode.OwnerDocument.CreateNode(XmlNodeType.Element, "RawSubTotal", string.Empty); decimal actualRawSubTotal = rawSubTotal + rawSubTotalVat; rawSubTotalNode.InnerText = XmlCommon.XmlEncode(actualRawSubTotal.ToString()); orderInfoNode.InsertAfter(rawSubTotalNode, orderInfoNode.LastChild); XmlNode discountsNode = orderInfoNode.OwnerDocument.CreateNode(XmlNodeType.Element, "Discounts", string.Empty); orderInfoNode.InsertAfter(discountsNode, orderInfoNode.LastChild); string couponCode = XmlCommon.XmlField(orderInfoNode, "CouponCode"); int couponType = XmlCommon.XmlFieldNativeInt(orderInfoNode, "CouponType"); bool hasCouponApplied = !string.IsNullOrEmpty(couponCode) && couponType != GIFTCARD_COUPONTYPE; decimal couponDiscountPercent = XmlCommon.XmlFieldNativeDecimal(orderInfoNode, "CouponDiscountPercent"); decimal couponDiscountAmount = XmlCommon.XmlFieldNativeDecimal(orderInfoNode, "CouponDiscountAmount"); if (hasCouponApplied) { // were only interested in the amount if (couponDiscountPercent > decimal.Zero) { XmlNode discountNode = orderInfoNode.OwnerDocument.CreateNode(XmlNodeType.Element, "Discount", string.Empty); XmlAttribute typeAttribute = orderInfoNode.OwnerDocument.CreateAttribute("type"); typeAttribute.Value = "Coupon"; //"Coupon Percent"; discountNode.Attributes.Append(typeAttribute); decimal couponDiscountPercentAmount = decimal.Zero; // check whether the coupon type is by percent // if it is, compute by line items included only string validProductIdCommaSeparated = XmlCommon.XmlField(orderInfoNode, "ValidProductsForCoupon"); if (!string.IsNullOrEmpty(validProductIdCommaSeparated) && couponType == PRODUCT_COUPONTYPE) { string[] validProductsIds = validProductIdCommaSeparated.Split(','); foreach (string productId in validProductsIds) { // let's find the line item this product is mapped to string xpath = string.Format("OrderItems/Item[ProductID={0}]", productId); XPathNavigator validProductNav = nav.SelectSingleNode(xpath); if (validProductNav != null) { XmlNode validProductNode = GetXmlNode(validProductNav); if (validProductNode != null) { decimal lineItemExtPrice = XmlCommon.XmlFieldNativeDecimal(validProductNode, "OrderedProductPrice"); if (incVat) { lineItemExtPrice += XmlCommon.XmlFieldNativeDecimal(validProductNode, "ExtVatAmount"); } decimal lineItemCouponDiscountAmount = lineItemExtPrice * (couponDiscountPercent / 100M); couponDiscountPercentAmount += lineItemCouponDiscountAmount; } } } } else { // coupon is applied to order if (couponDiscountPercent == 100M && subTotal == decimal.Zero) { couponDiscountPercentAmount = appliedSubTotal; } else { couponDiscountPercentAmount = (appliedSubTotal * (couponDiscountPercent / 100M)); } } XmlAttribute valueAttribute = orderInfoNode.OwnerDocument.CreateAttribute("value"); valueAttribute.Value = couponDiscountPercentAmount.ToString(); discountNode.Attributes.Append(valueAttribute); discountsNode.AppendChild(discountNode); // apply the discount at this one appliedSubTotal -= couponDiscountPercentAmount; } if (couponDiscountAmount > decimal.Zero && appliedSubTotal > decimal.Zero) { // check whether the coupon type is by percent // if it is, compute by line items included only string validProductIdCommaSeparated = XmlCommon.XmlField(orderInfoNode, "ValidProductsForCoupon"); if (!string.IsNullOrEmpty(validProductIdCommaSeparated) && couponType == PRODUCT_COUPONTYPE) { string[] validProductsIds = validProductIdCommaSeparated.Split(','); foreach (string productId in validProductsIds) { // let's find the line item this product is mapped to string xpath = string.Format("OrderItems/Item[ProductID={0}]", productId); XPathNavigator validProductNav = nav.SelectSingleNode(xpath); if (validProductNav != null) { XmlNode validProductNode = GetXmlNode(validProductNav); if (validProductNode != null) { decimal lineItemExtPrice = XmlCommon.XmlFieldNativeDecimal(validProductNode, "OrderedProductPrice"); decimal lineItemDiscountAmount = couponDiscountAmount; if (couponDiscountAmount > lineItemExtPrice) { couponDiscountAmount += lineItemExtPrice; } // need to take into account the quantity int qty = XmlCommon.XmlFieldNativeInt(validProductNode, "Quantity"); couponDiscountAmount *= qty; } } } } else if (couponDiscountAmount > appliedSubTotal) { couponDiscountAmount = appliedSubTotal; } XmlNode discountNode = orderInfoNode.OwnerDocument.CreateNode(XmlNodeType.Element, "Discount", string.Empty); XmlAttribute typeAttribute = orderInfoNode.OwnerDocument.CreateAttribute("type"); typeAttribute.Value = "Coupon Amount"; // "Coupon Amount"; discountNode.Attributes.Append(typeAttribute); XmlAttribute valueAttribute = orderInfoNode.OwnerDocument.CreateAttribute("value"); valueAttribute.Value = couponDiscountAmount.ToString(); discountNode.Attributes.Append(valueAttribute); discountsNode.AppendChild(discountNode); appliedSubTotal -= couponDiscountAmount; } } decimal levelDiscountAmount = XmlCommon.XmlFieldNativeDecimal(orderInfoNode, "LevelDiscountAmount"); if (XmlCommon.XmlFieldNativeInt(orderInfoNode, "LevelID") > 0 && levelDiscountAmount > 0) { if (levelDiscountAmount > decimal.Zero && appliedSubTotal > decimal.Zero) { if (levelDiscountAmount > appliedSubTotal) { levelDiscountAmount = appliedSubTotal; } } // were only interested in the amount XmlNode discountNode = orderInfoNode.OwnerDocument.CreateNode(XmlNodeType.Element, "Discount", string.Empty); XmlAttribute typeAttribute = orderInfoNode.OwnerDocument.CreateAttribute("type"); typeAttribute.Value = "Level"; //"Level" discountNode.Attributes.Append(typeAttribute); XmlAttribute valueAttribute = orderInfoNode.OwnerDocument.CreateAttribute("value"); valueAttribute.Value = levelDiscountAmount.ToString(); discountNode.Attributes.Append(valueAttribute); discountsNode.AppendChild(discountNode); appliedSubTotal -= levelDiscountAmount; } // gift card discount{shown as payment} decimal giftCardPaymentAppliedAmount = couponDiscountAmount; // we save the applied gift card value on the coupon discount amount col bool hasGiftCardApplied = (couponType == GIFTCARD_COUPONTYPE) && giftCardPaymentAppliedAmount > decimal.Zero; decimal orderTotal = XmlCommon.XmlFieldNativeDecimal(orderInfoNode, "OrderTotal"); decimal netTotal = orderTotal; if (hasGiftCardApplied) { // we have Gift Card Applied netTotal = orderTotal - giftCardPaymentAppliedAmount; } XmlNode hasgiftCardAppliedNode = orderInfoNode.OwnerDocument.CreateNode(XmlNodeType.Element, "HasGiftCardApplied", string.Empty); hasgiftCardAppliedNode.InnerText = XmlCommon.XmlEncode(hasGiftCardApplied.ToString()); orderInfoNode.InsertAfter(hasgiftCardAppliedNode, orderInfoNode.LastChild); XmlNode netTotalNode = orderInfoNode.OwnerDocument.CreateNode(XmlNodeType.Element, "NetTotal", string.Empty); netTotalNode.InnerText = XmlCommon.XmlEncode(netTotal.ToString()); orderInfoNode.InsertAfter(netTotalNode, orderInfoNode.LastChild); }
// context unchanged if node not found public XmlNode SetContext(String NodeName) { XmlNode n; if (NodeName.Length == 0) { return(null); } else { String NodeSpec = String.Format(@"//{0}[{1}/ml/locale[@name=/root/System/LocaleSetting]={2}]", m_NodeName, m_NameColumnName, DB.SQuote(XmlCommon.XmlEncode(NodeName))); n = m_XmlDoc.SelectSingleNode(NodeSpec); if (n == null) { // may not have <ml> markup on Name NodeSpec = String.Format(@"//{0}[Name={1}]", m_NodeName, DB.SQuote(XmlCommon.XmlEncode(NodeName))); n = m_XmlDoc.SelectSingleNode(NodeSpec); } } return(n); }