private static RenderingBlock GetRenderingFromTemp(TempBlock b) { var renderingBlocks = b.Blocks.Select(GetRenderingFromTemp); // recurse return(new RenderingBlock(b.Name, b.Source, renderingBlocks, b.Data, b.Fragment, b.Cache)); }
// input is a collection of block data values // defined at structure level or under a named blocks chain // output is the resulting collection of temp blocks // // named blocks are collapsed into one temp block each // anonymous blocks each match to one temp block // // blockDataValues is ordered bottom-top // returns blocks ordered top-bottom private static IEnumerable <TempBlock> GetTempFromData(IEnumerable <WithLevel <BlockDataValue> > blockDataValues) { var blockDataValuesArray = blockDataValues.ToArray(); // iterate only once // find named blocks // the name is case-insensitive var namedBlockDataValueDictionary = new Dictionary <string, List <WithLevel <BlockDataValue> > >(StringComparer.InvariantCultureIgnoreCase); foreach (var b in blockDataValuesArray.Where(x => x.Item.IsNamed)) { List <WithLevel <BlockDataValue> > namedBlockDataValues; if (!namedBlockDataValueDictionary.TryGetValue(b.Item.Name, out namedBlockDataValues)) { namedBlockDataValues = namedBlockDataValueDictionary[b.Item.Name] = new List <WithLevel <BlockDataValue> >(); } namedBlockDataValues.Add(b); } foreach (var kvp in namedBlockDataValueDictionary) { kvp.Value.Reverse(); // top-to-bottom } // create the temporary named blocks from the top-most block data value // whether bottom named blocks can change any of these settings is an option var namedTempBlocks = new Dictionary <string, TempBlock>(StringComparer.InvariantCultureIgnoreCase); foreach (var namedBlockDataValues in namedBlockDataValueDictionary.Values) { var blockDataValue = namedBlockDataValues[0].Item; var blockLevel = namedBlockDataValues[0].Level; var t = new TempBlock { Name = blockDataValue.Name, Source = blockDataValue.Source, Index = blockDataValue.Index, Fragment = blockDataValue.Fragment, Cache = blockDataValue.Cache }; t.MergeData(blockDataValue.Data); namedTempBlocks[blockDataValue.Name] = t; t.BlockDataValues.AddRange(blockDataValue.Blocks .Where(b => b.MinLevel <= blockLevel && b.MaxLevel >= blockLevel) .Select(x => new WithLevel <BlockDataValue>(x, blockLevel))); foreach (var namedOtherDataValue in namedBlockDataValues.Skip(1)) // top-bottom { var otherDataValue = namedOtherDataValue.Item; var otherLevel = namedOtherDataValue.Level; if (otherDataValue.IsKill) { t.Killed = true; break; // no need to continue } // set source if not already set if (!string.IsNullOrWhiteSpace(otherDataValue.Source)) { if (!string.IsNullOrWhiteSpace(t.Source)) { throw new StructureException("Cannot change source of a named blocks once it has been set."); } t.Source = otherDataValue.Source; } // index cannot change if (otherDataValue.Index != BlockDataValue.DefaultIndex) { throw new StructureException("Only the top-most named block can define an index."); } // merge data t.MergeData(otherDataValue.Data); // override fragment if (otherDataValue.Fragment != null) { t.Fragment = otherDataValue.Fragment; } // set cache if not already set if (otherDataValue.Cache != null) { if (t.Cache != null) { throw new StructureException("Cannot change cache of a named blocks once it has been set."); } t.Cache = otherDataValue.Cache; } if (otherDataValue.IsReset) { t.BlockDataValues.Clear(); } t.BlockDataValues.AddRange(otherDataValue.Blocks .Where(b => b.MinLevel <= otherLevel && b.MaxLevel >= otherLevel) .Select(x => new WithLevel <BlockDataValue>(x, otherLevel))); } t.BlockDataValues.Reverse(); } // build the temporary blocks list var tempBlocks = new List <TempBlock>(); foreach (var block in blockDataValuesArray) // bottom-top { var blockDataValue = block.Item; var blockLevel = block.Level; // check if it matches a named block TempBlock namedTempBlock; if (namedTempBlocks.TryGetValue(blockDataValue.Name, out namedTempBlock)) { // named block // insert temp block at the position of the top-most occurence var isTopMost = block == namedBlockDataValueDictionary[blockDataValue.Name][0]; if (isTopMost && !namedTempBlock.Killed) { tempBlocks.Add(namedTempBlock); } } else { // not a named block // just add a new temp block var t = new TempBlock { Name = string.Empty, // not a named block Source = blockDataValue.Source, Index = blockDataValue.Index, Fragment = blockDataValue.Fragment, Cache = blockDataValue.Cache, // there's nothing to merge so all blocks are defined at the same level // block.Item.Blocks is top-bottom, must reverse Blocks = GetTempFromData(block.Item.Blocks.Reverse() .Where(b => b.MinLevel <= blockLevel && b.MaxLevel >= blockLevel) .Select(b => new WithLevel <BlockDataValue>(b, blockLevel))) }; t.MergeData(blockDataValue.Data); tempBlocks.Add(t); } } // process named blocks source & inner blocks foreach (var tempBlock in namedTempBlocks.Values) { // for a named block, source may be missing and then we use the name as a source tempBlock.Source = string.IsNullOrWhiteSpace(tempBlock.Source) ? tempBlock.Name : tempBlock.Source; // tempBlock is a named block so tempBlock.BlockDatas is already bottom-top ordered tempBlock.Blocks = GetTempFromData(tempBlock.BlockDataValues); // recurse } tempBlocks.Reverse(); // return top-bottom // sort according to indexes // beware! List<T>.Sort() is documented as performing an unstable sort // whereas Enumerable.OrderBy<TSource, TKey>.Sort() is documented as performing a stable sort, // a stable sort meaning that when indexes are equals, the existing order is preserved //tempBlocks.Sort((b1, b2) => b1.Index - b2.Index); // NOT! tempBlocks = tempBlocks.OrderBy(x => x.Index).ToList(); return(tempBlocks); }