private void CanonicalizeChildren(XmlElement origElement, XmlElement newElement, NrdoTable type, XmlElement beforeElement, XmlElement afterElement) { XmlElement preFind = null; bool parentIsDelete = XmlUtil.GetAttr(newElement, "nrdo.action") == "delete"; foreach (XmlElement childElement in new List <XmlElement>(Recipe.elementChildren(origElement))) { string name = childElement.LocalName; string action = XmlUtil.GetAttr(childElement, "nrdo.action"); // If it names a field whose type is string, grab either the text or the InnerXml of it (depending on the // "escaped" attribute) and put it in an attribute on newElement, erroring if such attribute already exists. NrdoField field = type.GetField(name); if (field != null && field.Type.Equals(typeof(string))) { if (newElement.HasAttribute(field.Name)) { throw new ArgumentException("Cannot specify " + field.Name + " as both attribute and nested element on " + type.Name); } bool escaped; if (childElement.HasAttribute("escaped")) { switch (XmlUtil.GetAttr(childElement, "escaped")) { case "true": escaped = true; break; case "false": escaped = false; break; default: throw new ArgumentException("escaped attribute must have value 'true' or 'false', not " + XmlUtil.GetAttr(childElement, "escaped")); } } else { escaped = false; } // FIXME: should check for any elements here if (escaped), they're illegal and should be thrown out string value = escaped ? childElement.InnerText : childElement.InnerXml; newElement.SetAttribute(field.Name, value); if (preFind != null) { preFind.SetAttribute(field.Name, value); } continue; } // If it names an eligible reference, then // - CanonicalizeElement(childElement, targetType, beforeElement) and save the result. // - If the result doesn't have a nrdo.id, add one from origElement (or error if it doesn't have one) // - Add a reference-named attribute to newElement with the nrdo.id of the new element. NrdoSingleReference reference = null; foreach (NrdoReference testRef in type.References) { if (testRef.Name == name && Recipe.isEligibleReference(testRef) && testRef.AssociatedGet == testRef.TargetTable.PkeyGet && testRef.TargetTable.IsPkeySequenced) { reference = (NrdoSingleReference)testRef; break; } } if (reference != null) { // If the parent element is being deleted, the child element MUST be deleted too. if (parentIsDelete) { if (action != "none" && action != "delete") { throw new ApplicationException(childElement.LocalName + " element inside deleted " + newElement.LocalName + " element must also have nrdo.action='delete'"); } } else if (action == "delete") { throw new ApplicationException(childElement.LocalName + " element cannot have nrdo.action='delete' because containing " + newElement.LocalName + " element doesn't"); } if (preFind == null) { preFind = newElement.OwnerDocument.CreateElement(newElement.LocalName); foreach (XmlAttribute attr in newElement.Attributes) { preFind.SetAttribute(attr.Name, attr.Value); } if (!parentIsDelete) { preFind.SetAttribute("nrdo.action", "none"); } bool insertPreFind = true; if (!preFind.HasAttribute("nrdo.id")) { foreach (NrdoFieldRef pkeyField in type.PkeyGet.Fields) { if (!preFind.HasAttribute(pkeyField.Field.Name)) { insertPreFind = false; } } } if (insertPreFind) { beforeElement.ParentNode.InsertBefore(preFind, beforeElement); } else if (parentIsDelete) { throw new ApplicationException("Cannot construct correct order for deleting " + childElement.LocalName + " without a nrdo.id, either add the right nrdo.id or reorder the elements manually"); } } if (!childElement.HasAttribute("nrdo.id")) { if (!newElement.HasAttribute("nrdo.id")) { throw new ArgumentException(childElement.Name + " element must have a nrdo.id attribute."); } childElement.SetAttribute("nrdo.id", XmlUtil.GetAttr(newElement, "nrdo.id") + "_" + reference.Name); } // This needs to be done before canonicalizing, because the canonicalization can actually // result in two records (the child may itself end up turning into a 'preFind') and only // one of these gets returned. if (newElement.HasAttribute("nrdo.id")) { childElement.SetAttribute(reference.Joins[0].To.Field.Name, ":" + XmlUtil.GetAttr(newElement, "nrdo.id") + "." + reference.Joins[0].From.Field.Name); } XmlElement result = CanonicalizeElement(childElement, reference.TargetTable, beforeElement); if (newElement.HasAttribute(reference.Name)) { throw new ArgumentException("Cannot specify " + reference.Name + " as both attribute and nested element on " + type.Name); } newElement.SetAttribute(reference.Name, XmlUtil.GetAttr(result, "nrdo.id")); continue; } // Otherwise it should name a table. If it contains a ".", get the table outright. Otherwise start from // the module that "type" is in, trying NrdoTable.GetTable on each until something is found. If nothing is, it's // an error. NrdoTable table = null; if (name.IndexOf('.') >= 0) { table = NrdoTable.GetTable(Lookup, name.Replace('.', ':')); } else { string module = type.Module; while (module != null && table == null) { table = NrdoTable.GetTable(Lookup, module + ":" + name); int pos = module.LastIndexOf(':'); module = pos < 0 ? null : module.Substring(0, pos); } if (table == null) { table = NrdoTable.GetTable(Lookup, name); } } if (table == null) { throw new ArgumentException("No table called " + name + " found from " + type.Module); } // Find all eligible references from that table to "type". If there's not exactly one, error. Otherwise take that // reference. List <NrdoSingleReference> refs = new List <NrdoSingleReference>(); foreach (NrdoReference testRef in table.References) { if (Recipe.isEligibleReference(testRef) && testRef.TargetTable.Name == type.Name) { refs.Add((NrdoSingleReference)testRef); } } if (refs.Count != 1) { throw new ArgumentException("There isn't a single eligible reference from " + table.Name + " to " + type.Name); } if (parentIsDelete && action != "none" && action != "delete") { throw new ApplicationException(childElement.LocalName + " element inside deleted " + newElement.LocalName + " element must also have nrdo.action='delete'"); } else if (parentIsDelete && action == "delete") { // FIXME: this needs to somehow turn into <outer nrdo.action="none"/><inner nrdo.action="delete"/><outer nrdo.action="delete"/> throw new ArgumentException("Not yet implemented: cannot use 'back-references as nested element' construct between two elements that are both being deleted (" + newElement.LocalName + " and " + childElement.LocalName + ")"); } else { // CanonicalizeElement(childElement, tableType, afterElement) and save the result. // Add a reference-named attribute to the result with the nrdo.id of the current element if (!newElement.HasAttribute("nrdo.id")) { throw new ArgumentException("Cannot use 'back-references as nested element' construct on a " + type.Name + " element without a nrdo.id"); } XmlElement refResult = CanonicalizeElement(childElement, table, afterElement); if (refResult.HasAttribute(refs[0].Name)) { throw new ArgumentException("Cannot specify " + refs[0].Name + " attribute directly on " + name + " when nesting inside a " + type.Name); } refResult.SetAttribute(refs[0].Name, XmlUtil.GetAttr(newElement, "nrdo.id")); } } }
internal object GetValue(NrdoField field) { return(getValue(field)); }
internal object evaluate(Type type, string expr) { if (expr.StartsWith(":")) { if (expr.StartsWith("::")) { expr = expr.Substring(1); } else if (expr == ":null") { return(null); } else if (expr == ":now") { if (type.Equals(typeof(DateTime))) { return(Now.Value); } else { throw new ArgumentException(":now can only be used on fields of type DateTime, not " + type.FullName); } } else { string[] parts = expr.Substring(1).Split('.'); if (parts.Length != 2 || parts[0].Length == 0 || parts[1].Length == 0) { throw new ArgumentException("Values that start with : must either be :null, start with :: (for a value that really starts with ':') or be of the form :nrdoid.fieldname. '" + expr + "' is none of these"); } string nrdoId = parts[0]; string fieldName = parts[1]; RecipeRecord record = GetRecord(nrdoId); if (record == null) { return(Undefined.Value); } NrdoField field = record.Table.GetField(fieldName); if (field == null) { throw new ArgumentException("nrdo.id " + nrdoId + " is a " + record.Table.Name + " which does not contain a field called " + fieldName); } object value = field.Get(record.GetData()); if (value == null || value.GetType() == type) { return(value); } else { try { return(TypeDescriptor.GetConverter(type).ConvertFrom(value)); } catch (NotSupportedException) { expr = value.ToString(); } } } } return(TypeDescriptor.GetConverter(type).ConvertFromString(expr)); }