public async Task CreateResolvedEntityAsync()

in objectModel/CSharp/Microsoft.CommonDataModel.ObjectModel/Cdm/CdmEntityDefinition.cs [550:1104]


        public async Task<CdmEntityDefinition> CreateResolvedEntityAsync(string newEntName, ResolveOptions resOpt = null, CdmFolderDefinition folder = null, string newDocName = null)
        {
            using (Logger.EnterScope(nameof(CdmEntityDefinition), Ctx, nameof(CreateResolvedEntityAsync)))
            {
                if (resOpt == null)
                {
                    resOpt = new ResolveOptions(this, this.Ctx.Corpus.DefaultResolutionDirectives);
                }

                if (resOpt.WrtDoc == null)
                {
                    Logger.Error((ResolveContext)this.Ctx, Tag, nameof(CreateResolvedEntityAsync), this.AtCorpusPath, CdmLogCode.ErrDocWrtDocNotfound);
                    return null;
                }

                if (string.IsNullOrEmpty(newEntName))
                {
                    Logger.Error((ResolveContext)this.Ctx, Tag, nameof(CreateResolvedEntityAsync), this.AtCorpusPath, CdmLogCode.ErrResolveNewEntityNameNotSet);
                    return null;
                }

                // if the wrtDoc needs to be indexed (like it was just modified) then do that first
                if (!await resOpt.WrtDoc.IndexIfNeeded(resOpt, true))
                {
                    Logger.Error((ResolveContext)this.Ctx, Tag, nameof(CreateResolvedEntityAsync), this.AtCorpusPath, CdmLogCode.ErrIndexFailed);
                    return null;
                }

                if (folder == null)
                {
                    folder = this.InDocument.Folder;
                }

                string fileName = (string.IsNullOrEmpty(newDocName)) ? $"{newEntName}.cdm.json" : newDocName;
                string origDoc = this.InDocument.AtCorpusPath;

                // Don't overwite the source document
                string targetAtCorpusPath = $"{this.Ctx.Corpus.Storage.CreateAbsoluteCorpusPath(folder.AtCorpusPath, folder)}{fileName}";
                if (StringUtils.EqualsWithIgnoreCase(targetAtCorpusPath, origDoc))
                {
                    Logger.Error((ResolveContext)this.Ctx, Tag, nameof(CreateResolvedEntityAsync), this.AtCorpusPath, CdmLogCode.ErrDocEntityReplacementFailure, targetAtCorpusPath);
                    return null;
                }

                // make sure the corpus has a set of default artifact attributes 
                await this.Ctx.Corpus.PrepareArtifactAttributesAsync();

                // make the top level attribute context for this entity
                // for this whole section where we generate the attribute context tree and get resolved attributes
                // set the flag that keeps all of the parent changes and document dirty from from happening 
                bool wasResolving = this.Ctx.Corpus.isCurrentlyResolving;
                this.Ctx.Corpus.isCurrentlyResolving = true;
                string entName = newEntName;
                ResolveContext ctx = this.Ctx as ResolveContext;
                CdmAttributeContext attCtxEnt = ctx.Corpus.MakeObject<CdmAttributeContext>(CdmObjectType.AttributeContextDef, entName, true);
                attCtxEnt.Ctx = ctx;
                attCtxEnt.InDocument = this.InDocument;

                // cheating a bit to put the paths in the right place
                AttributeContextParameters acp = new AttributeContextParameters
                {
                    under = attCtxEnt,
                    type = CdmAttributeContextType.AttributeGroup,
                    Name = "attributeContext"
                };
                CdmAttributeContext attCtxAC = CdmAttributeContext.CreateChildUnder(resOpt, acp);
                // this is the node that actually is first in the context we save. all definition refs should take the new perspective that they
                // can only be understood through the resolvedFrom moniker
                CdmEntityReference entRefThis = ctx.Corpus.MakeObject<CdmEntityReference>(CdmObjectType.EntityRef, this.GetName(), true);
                entRefThis.Owner = this;
                entRefThis.InDocument = this.InDocument; // need to set owner and inDocument to this starting entity so the ref will be portable to the new document
                CdmObject prevOwner = this.Owner;
                entRefThis.ExplicitReference = this;
                // we don't want to change the owner of this entity to the entity reference
                // re-assign whatever was there before
                this.Owner = prevOwner;
                AttributeContextParameters acpEnt = new AttributeContextParameters
                {
                    under = attCtxAC,
                    type = CdmAttributeContextType.Entity,
                    Name = entName,
                    Regarding = entRefThis
                };

                // reset previous depth information in case there are left overs
                resOpt.DepthInfo.Reset();

                ResolveOptions resOptCopy = CdmAttributeContext.PrepareOptionsForResolveAttributes(resOpt);

                // resolve attributes with this context. the end result is that each resolved attribute
                // points to the level of the context where it was last modified, merged, created
                var ras = this.FetchResolvedAttributes(resOptCopy, acpEnt);

                if (resOptCopy.UsedResolutionGuidance)
                {
                    Logger.Warning(ctx, Tag, nameof(CreateResolvedEntityAsync), this.AtCorpusPath, CdmLogCode.WarnDeprecatedResolutionGuidance);
                }

                if (ras == null)
                {
                    return null;
                }

                this.Ctx.Corpus.isCurrentlyResolving = wasResolving;

                // make a new document in given folder if provided or the same folder as the source entity
                folder.Documents.Remove(fileName);
                CdmDocumentDefinition docRes = folder.Documents.Add(fileName);
                // add a import of the source document 
                origDoc = this.Ctx.Corpus.Storage.CreateRelativeCorpusPath(origDoc, docRes); // just in case we missed the prefix
                docRes.Imports.Add(origDoc, "resolvedFrom");
                docRes.DocumentVersion = this.InDocument.DocumentVersion;
                // make the empty entity
                CdmEntityDefinition entResolved = docRes.Definitions.Add(entName);

                // grab that context that comes from fetchResolvedAttributes. We are sure this tree is a copy that we can own, so no need to copy it again
                CdmAttributeContext attCtx = null;
                if (attCtxAC != null && attCtxAC.Contents != null && attCtxAC.Contents.Count == 1)
                {
                    attCtx = attCtxAC.Contents[0] as CdmAttributeContext;
                }
                entResolved.AttributeContext = attCtx;

                if (attCtx != null)
                {
                    // fix all of the definition references, parent and lineage references, owner documents, etc. in the context tree
                    attCtx.FinalizeAttributeContext(resOptCopy, $"{entName}/attributeContext/", docRes, this.InDocument, "resolvedFrom", true);

                    // TEST CODE
                    // run over the resolved attributes and make sure none have the dummy context
                    //Action<ResolvedAttributeSet> testResolveAttributeCtx = null;
                    //testResolveAttributeCtx = (rasSub) =>
                    //{
                    //    if (rasSub.Set.Count != 0 && rasSub.AttributeContext.AtCorpusPath.StartsWith("cacheHolder"))
                    //        System.Diagnostics.Debug.WriteLine("Bad");
                    //    rasSub.Set.ForEach(ra =>
                    //    {
                    //        if (ra.AttCtx.AtCorpusPath.StartsWith("cacheHolder"))
                    //            System.Diagnostics.Debug.WriteLine("Bad");

                    //        // the target for a resolved att can be a typeAttribute OR it can be another resolvedAttributeSet (meaning a group)
                    //        if (ra.Target is ResolvedAttributeSet)
                    //        {
                    //            testResolveAttributeCtx(ra.Target as ResolvedAttributeSet);
                    //        }
                    //    });
                    //};
                    //testResolveAttributeCtx(ras);

                }

                // add the traits of the entity, also add to attribute context top node
                ResolvedTraitSet rtsEnt = this.FetchResolvedTraits(resOpt);
                rtsEnt.Set.ForEach(rt =>
                {
                    var traitRef = CdmObjectBase.ResolvedTraitToTraitRef(resOptCopy, rt);
                    (entResolved as CdmObjectDefinition).ExhibitsTraits.Add(traitRef);
                    traitRef = CdmObjectBase.ResolvedTraitToTraitRef(resOptCopy, rt); // fresh copy
                    entResolved.AttributeContext?.ExhibitsTraits.Add(traitRef);
                });

                // special trait to explain this is a resolved entity
                entResolved.IndicateAbstractionLevel("resolved", resOpt);

                if (entResolved.AttributeContext != null)
                {
                    // the attributes have been named, shaped, etc for this entity so now it is safe to go and 
                    // make each attribute context level point at these final versions of attributes

                    // what we have is a resolved attribute set (maybe with structure) where each ra points at the best tree node
                    // we also have the tree of context, we need to flip this around so that the right tree nodes point at the paths to the
                    // right attributes. so run over the resolved atts and then add a path reference to each one into the context contents where is last lived
                    IDictionary<string, int> attPath2Order = new Dictionary<string, int>();
                    HashSet<string> finishedGroups = new HashSet<string>();
                    HashSet<CdmAttributeContext> allPrimaryCtx = new HashSet<CdmAttributeContext>(); // keep a list so it is easier to think about these later
                    Action<ResolvedAttributeSet, string> pointContextAtResolvedAtts = null;
                    pointContextAtResolvedAtts = (rasSub, path) =>
                    {
                        rasSub.Set.ForEach(ra =>
                        {
                            var raCtx = ra.AttCtx;
                            var refs = raCtx.Contents;
                            allPrimaryCtx.Add(raCtx);

                            string attRefPath = path + ra.ResolvedName;
                            // the target for a resolved att can be a typeAttribute OR it can be another resolvedAttributeSet (meaning a group)
                            if (ra.Target is CdmAttribute)
                            {
                                // it was an attribute, add to the content of the context, also, keep track of the ordering for all of the att paths we make up
                                // as we go through the resolved attributes, this is the order of atts in the final resolved entity
                                if (!attPath2Order.ContainsKey(attRefPath))
                                {
                                    var attRef = this.Ctx.Corpus.MakeObject<CdmObjectReferenceBase>(CdmObjectType.AttributeRef, attRefPath, true);
                                    // only need one explanation for this path to the insert order
                                    attPath2Order.Add(attRef.NamedReference, ra.InsertOrder);
                                    raCtx.Contents.Add(attRef);
                                }
                            }
                            else
                            {
                                // a group, so we know an attribute group will get created later with the name of the group and the things it contains will be in
                                // the members of that group
                                attRefPath += "/members/";
                                if (!finishedGroups.Contains(attRefPath))
                                {
                                    pointContextAtResolvedAtts(ra.Target as ResolvedAttributeSet, attRefPath);
                                    finishedGroups.Add(attRefPath);
                                }
                            }
                        });
                    };

                    pointContextAtResolvedAtts(ras, entName + "/hasAttributes/");

                    // the generated attribute structures sometimes has a LOT of extra nodes that don't say anything or explain anything
                    // our goal now is to prune that tree to just have the stuff one may need
                    // do this by keeping the leafs and all of the lineage nodes for the attributes that end up in the resolved entity
                    // along with some special nodes that explain entity structure and inherit
                    //System.Diagnostics.Debug.Write($"res ent {entName}");
                    if (!attCtx.PruneToScope(allPrimaryCtx))
                    {
                        // TODO: log error
                        return null;
                    }

                    // create an all-up ordering of attributes at the leaves of this tree based on insert order
                    // sort the attributes in each context by their creation order and mix that with the other sub-contexts that have been sorted
                    Func<CdmAttributeContext, int?> orderContents = null;
                    Func<CdmObject, int?> getOrderNum = (item) =>
                    {
                        if (item.ObjectType == CdmObjectType.AttributeContextDef && orderContents != null)
                        {
                            return orderContents(item as CdmAttributeContext);
                        }
                        else if (item.ObjectType == CdmObjectType.AttributeRef)
                        {
                            string attName = (item as CdmAttributeReference).NamedReference;
                            int o = attPath2Order[attName];
                            return o;
                        }
                        else
                        {
                            return -1; // put the mystery item on top.
                        }
                    };

                    orderContents = (CdmAttributeContext under) =>
                    {
                        if (under.LowestOrder == null)
                        {
                            under.LowestOrder = -1; // used for group with nothing but traits
                            if (under.Contents.Count == 1)
                            {
                                under.LowestOrder = getOrderNum(under.Contents[0]);
                            }
                            else
                            {
                                under.Contents.AllItems.Sort((l, r) =>
                                {
                                    int lNum = -1;
                                    int rNum = -1;

                                    int? aux;
                                    aux = getOrderNum(l);

                                    if (aux != null)
                                    {
                                        lNum = (int)aux;
                                    }

                                    aux = getOrderNum(r);

                                    if (aux != null)
                                    {
                                        rNum = (int)aux;
                                    }

                                    if (lNum != -1 && (under.LowestOrder == -1 || lNum < under.LowestOrder))
                                        under.LowestOrder = lNum;
                                    if (rNum != -1 && (under.LowestOrder == -1 || rNum < under.LowestOrder))
                                        under.LowestOrder = rNum;

                                    return lNum - rNum;
                                });
                            }
                        }
                        return under.LowestOrder;
                    };

                    orderContents(attCtx);

                    // resolved attributes can gain traits that are applied to an entity when referenced
                    // since these traits are described in the context, it is redundant and messy to list them in the attribute
                    // so, remove them. create and cache a set of names to look for per context 
                    // there is actually a hierarchy to all attributes from the base entity should have all traits applied independently of the 
                    // sub-context they come from. Same is true of attribute entities. so do this recursively top down
                    var ctx2traitNames = new Dictionary<CdmAttributeContext, HashSet<string>>();
                    Action<CdmAttributeContext, HashSet<string>> collectContextTraits = null;

                    collectContextTraits = (subAttCtx, inheritedTraitNames) =>
                    {
                        var traitNamesHere = new HashSet<string>(inheritedTraitNames);
                        var traitsHere = subAttCtx.ExhibitsTraits;
                        if (traitsHere != null)
                            foreach (var tat in traitsHere) { traitNamesHere.Add(tat.NamedReference); }
                        ctx2traitNames.Add(subAttCtx, traitNamesHere);
                        subAttCtx.Contents.AllItems.ForEach((cr) =>
                        {
                            if (cr.ObjectType == CdmObjectType.AttributeContextDef)
                            {
                                // do this for all types?
                                collectContextTraits(cr as CdmAttributeContext, traitNamesHere);
                            }
                        });

                    };
                    collectContextTraits(attCtx, new HashSet<string>());

                    // add the attributes, put them in attribute groups if structure needed
                    IDictionary<ResolvedAttribute, string> resAtt2RefPath = new Dictionary<ResolvedAttribute, string>();
                    Action<ResolvedAttributeSet, dynamic, string> addAttributes = null;
                    addAttributes = (rasSub, container, path) =>
                    {
                        rasSub.Set.ForEach(ra =>
                        {
                            string attPath = path + ra.ResolvedName;
                            // use the path of the context associated with this attribute to find the new context that matches on path
                            CdmAttributeContext raCtx = ra.AttCtx;

                            if (ra.Target is ResolvedAttributeSet)
                            {
                                // this is a set of attributes.
                                // make an attribute group to hold them
                                CdmAttributeGroupDefinition attGrp = this.Ctx.Corpus.MakeObject<CdmAttributeGroupDefinition>(CdmObjectType.AttributeGroupDef, ra.ResolvedName, false);
                                attGrp.AttributeContext = this.Ctx.Corpus.MakeObject<CdmAttributeContextReference>(CdmObjectType.AttributeContextRef, raCtx.AtCorpusPath, true);
                                // debugLineage
                                //attGrp.AttributeContext.NamedReference = $"{ raCtx.AtCoprusPath}({raCtx.Id})";

                                // take any traits from the set and make them look like traits exhibited by the group
                                HashSet<string> avoidSet = ctx2traitNames[raCtx];
                                // traits with the same name can show up on entities and attributes AND have different meanings.
                                avoidSet.Clear();

                                ResolvedTraitSet rtsAtt = ra.ResolvedTraits;
                                rtsAtt.Set.ForEach(rt =>
                                {
                                    if (rt.Trait.Ugly != true)
                                    {
                                        // don't mention your ugly traits
                                        if (avoidSet?.Contains(rt.TraitName) != true)
                                        {
                                            // avoid the ones from the context
                                            var traitRef = CdmObjectBase.ResolvedTraitToTraitRef(resOptCopy, rt);
                                            (attGrp as CdmObjectDefinitionBase).ExhibitsTraits.Add(traitRef);
                                        }
                                    }
                                });

                                // wrap it in a reference and then recurse with this as the new container
                                CdmAttributeGroupReference attGrpRef = this.Ctx.Corpus.MakeObject<CdmAttributeGroupReference>(CdmObjectType.AttributeGroupRef, null, false);
                                attGrpRef.ExplicitReference = attGrp;
                                container.AddAttributeDef(attGrpRef);
                                // isn't this where ...
                                addAttributes(ra.Target as ResolvedAttributeSet, attGrp, attPath + "/members/");
                            }
                            else
                            {
                                CdmTypeAttributeDefinition att = this.Ctx.Corpus.MakeObject<CdmTypeAttributeDefinition>(CdmObjectType.TypeAttributeDef, ra.ResolvedName, false);
                                att.AttributeContext = this.Ctx.Corpus.MakeObject<CdmAttributeContextReference>(CdmObjectType.AttributeContextRef, raCtx.AtCorpusPath, true);
                                // debugLineage
                                //att.AttributeContext.NamedReference = $"{ raCtx.AtCoprusPath}({raCtx.Id})";

                                HashSet<string> avoidSet = ctx2traitNames[raCtx];
                                // i don't know why i thought this was the right thing to do,
                                // traits with the same name can show up on entities and attributes AND have different meanings.
                                avoidSet.Clear();
                                // i really need to figure out the effects of this before making this change
                                // without it, some traits don't make it to the resolved entity
                                // with it, too many traits might make it there

                                ResolvedTraitSet rtsAtt = ra.ResolvedTraits;
                                rtsAtt.Set.ForEach(rt =>
                                {
                                    if (rt.Trait.Ugly != true)
                                    { // don't mention your ugly traits
                                        if (avoidSet?.Contains(rt.TraitName) != true)
                                        { // avoid the ones from the context
                                            var traitRef = CdmObjectBase.ResolvedTraitToTraitRef(resOptCopy, rt);
                                            ((CdmTypeAttributeDefinition)att).AppliedTraits.Add(traitRef);

                                            // the trait that points at other entities for foreign keys, that is trouble
                                            // the string value in the table needs to be a relative path from the document of this entity
                                            // to the document of the related entity. but, right now it is a relative path to the source entity
                                            // so find those traits, and adjust the paths in the tables they hold
                                            if (rt.TraitName == "is.linkedEntity.identifier")
                                            {
                                                // grab the content of the table from the new ref (should be a copy of orig) 
                                                List<List<string>> linkTable = null;
                                                if (traitRef.Arguments != null && traitRef.Arguments.Count > 0)
                                                {
                                                    linkTable = ((traitRef.Arguments?[0].Value as CdmEntityReference)?
                                                                    .ExplicitReference as CdmConstantEntityDefinition)?
                                                                    .ConstantValues;
                                                }
                                                if (linkTable != null && linkTable.Count > 0)
                                                {
                                                    foreach (var row in linkTable)
                                                    {
                                                        if (row.Count == 2 || row.Count == 3) // either the old table or the new one with relationship name can be there
                                                        {
                                                            // entity path an attribute name
                                                            string fixedPath = row[0];
                                                            fixedPath = this.Ctx.Corpus.Storage.CreateAbsoluteCorpusPath(fixedPath, this.InDocument); // absolute from relative to this
                                                            fixedPath = this.Ctx.Corpus.Storage.CreateRelativeCorpusPath(fixedPath, docRes); // realtive to new entity
                                                            row[0] = fixedPath;
                                                        }
                                                    }
                                                }
                                            }
                                        }
                                    }
                                });

                                // none of the dataformat traits have the bit set that will make them turn into a property
                                // this is intentional so that the format traits make it into the resolved object
                                // but, we still want a guess as the data format, so get it and set it.
                                var impliedDataFormat = att.DataFormat;
                                if (impliedDataFormat != CdmDataFormat.Unknown)
                                    att.DataFormat = impliedDataFormat;


                                container.AddAttributeDef(att);
                                resAtt2RefPath[ra] = attPath;
                            }
                        });
                    };
                    addAttributes(ras, entResolved, entName + "/hasAttributes/");

                    // any resolved traits that hold arguments with attribute refs should get 'fixed' here
                    Action<CdmTraitReference, string, bool> replaceTraitAttRef = (tr, entityHint, isAttributeContext) =>
                    {
                        if (tr.Arguments != null)
                        {
                            foreach (CdmArgumentDefinition arg in tr.Arguments.AllItems)
                            {
                                dynamic v = arg.UnResolvedValue != null ? arg.UnResolvedValue : arg.Value;
                                // is this an attribute reference?
                                if (v != null && (v as CdmObject)?.ObjectType == CdmObjectType.AttributeRef)
                                {
                                    // only try this if the reference has no path to it (only happens with intra-entity att refs)
                                    var attRef = v as CdmAttributeReference;
                                    if (!string.IsNullOrEmpty(attRef.NamedReference) && attRef.NamedReference.IndexOf('/') == -1)
                                    {
                                        if (arg.UnResolvedValue == null)
                                            arg.UnResolvedValue = arg.Value;

                                        // give a promise that can be worked out later. assumption is that the attribute must come from this entity.
                                        var newAttRef = this.Ctx.Corpus.MakeRef<CdmAttributeReference>(CdmObjectType.AttributeRef, entityHint + "/(resolvedAttributes)/" + attRef.NamedReference, true);
                                        // inDocument is not propagated during resolution, so set it here
                                        newAttRef.InDocument = arg.InDocument;
                                        arg.Value = newAttRef;
                                    }
                                }
                            }
                        }
                    };

                    // fix entity traits
                    if (entResolved.ExhibitsTraits != null)
                        foreach (var et in entResolved.ExhibitsTraits)
                        {
                            if (et is CdmTraitReference)
                            {
                                replaceTraitAttRef(et as CdmTraitReference, newEntName, false); 
                            }
                        }

                    // fix context traits
                    Action<CdmAttributeContext, string> fixContextTraits = null;
                    fixContextTraits = (subAttCtx, entityHint) =>
                    {
                        var traitsHere = subAttCtx.ExhibitsTraits;
                        if (traitsHere != null)
                        {
                            foreach (var tr in traitsHere)
                            {
                                if (tr is CdmTraitReference)
                                {
                                    replaceTraitAttRef(tr as CdmTraitReference, entityHint, true);
                                }
                            }
                        }
                        subAttCtx.Contents.AllItems.ForEach((cr) =>
                        {
                            if (cr.ObjectType == CdmObjectType.AttributeContextDef)
                            {
                                // if this is a new entity context, get the name to pass along
                                CdmAttributeContext subSubAttCtx = (CdmAttributeContext)cr;
                                string subEntityHint = entityHint;
                                if (subSubAttCtx.Type == CdmAttributeContextType.Entity && subSubAttCtx.Definition != null)
                                {
                                    subEntityHint = subSubAttCtx.Definition.NamedReference;
                                }
                                // do this for all types
                                fixContextTraits(subSubAttCtx, subEntityHint);
                            }
                        });

                    };
                    fixContextTraits(attCtx, newEntName);
                    // and the attribute traits
                    var entAttributes = entResolved.Attributes;
                    if (entAttributes != null)
                    {
                        foreach (var entAtt in entAttributes)
                        {
                            CdmTraitCollection attTraits = entAtt.AppliedTraits;
                            if (attTraits != null)
                            {
                                foreach (var tr in attTraits)
                                {
                                    if (tr is CdmTraitReference)
                                    {
                                        replaceTraitAttRef(tr as CdmTraitReference, newEntName, false);
                                    }
                                }
                            }
                        }
                    }
                }

                // we are about to put this content created in the context of various documents (like references to attributes from base entities, etc.)
                // into one specific document. all of the borrowed refs need to work. so, re-write all string references to work from this new document
                // the catch-22 is that the new document needs these fixes done before it can be used to make these fixes.
                // the fix needs to happen in the middle of the refresh
                // trigger the document to refresh current content into the resolved OM
                if (attCtx != null)
                    attCtx.Parent = null; // remove the fake parent that made the paths work
                ResolveOptions resOptNew = resOpt.Copy();
                resOptNew.LocalizeReferencesFor = docRes;
                resOptNew.WrtDoc = docRes;
                if (!await docRes.RefreshAsync(resOptNew))
                {
                    Logger.Error((ResolveContext)this.Ctx, Tag, nameof(CreateResolvedEntityAsync), this.AtCorpusPath, CdmLogCode.ErrIndexFailed);
                    return null;
                }

                // get a fresh ref
                entResolved = docRes.FetchObjectFromDocumentPath(entName, resOptNew) as CdmEntityDefinition;

                this.Ctx.Corpus.resEntMap[this.AtCorpusPath] = entResolved.AtCorpusPath;

                return entResolved;
            }
        }