private async Task InstructListRenderingAsync(XElement list, Dictionary <string, string> valuesByReference)
        {
            AssertElementName(list, "list");

            _context = _context.BeforeBegin(list.Name.LocalName, hasContent: list.HasContent());

            var listNestingLevel = _context.ListNestingLevel;

            if (!_listItemIndexContinueOffsetByNestingLevel.TryGetValue(listNestingLevel, out int listItemIndexContinueOffset))
            {
                _listItemIndexContinueOffsetByNestingLevel[listNestingLevel] = listItemIndexContinueOffset = 0;
            }

            var startAttribute = list.Attributes().FirstOrDefault(a => a.Name == "start");

            if (startAttribute == null || startAttribute.Value != "continue")
            {
                _listItemIndexContinueOffsetByNestingLevel[listNestingLevel] = listItemIndexContinueOffset = 0;
            }

            await _builder.BeginWriteListAsync(listItemIndexContinueOffset, _context);

            _context = _context.AfterBegin();

            var listItems = list.Elements().ToList();
            var conditionallyExcludedListItems = 0;

            for (var i = 0; i < listItems.Count; i++)
            {
                var listItem = listItems[i];
                AssertElementName(listItem, "list-item");

                await WriteConditionalElementAsync(
                    listItem,
                    valuesByReference,
                    async() =>
                {
                    int continuedListIndex = listItemIndexContinueOffset + i - conditionallyExcludedListItems;
                    _context = _context.BeforeBeginListItem(continuedListIndex, hasChildren: listItem.HasContent());
                    await _builder.BeginWriteListItemAsync(new ListIndexPath(_context.ListItemIndexPath), _context);
                    _context = _context.AfterBegin();

                    await TraverseContainerElementAsync(listItem, valuesByReference);

                    _context = _context.BeforeEnd();
                    await _builder.EndWriteListItemAsync(_context);
                    _context = _context.AfterEndListItem();
                },
                    () => conditionallyExcludedListItems ++);
            }

            _context = _context.BeforeEnd();
            await _builder.EndWriteListAsync(_context);

            _context = _context.AfterEnd();

            _listItemIndexContinueOffsetByNestingLevel[listNestingLevel] += listItems.Count;
        }
        private async Task InstructBlockRenderingAsync(XElement block, Dictionary <string, string> valuesByReference)
        {
            AssertElementName(block, "block");

            _context = _context.BeforeBegin(block.Name.LocalName, hasContent: block.HasContent());
            await _builder.BeginWriteBlockAsync(_context);

            _context = _context.AfterBegin();

            await TraverseContainerElementAsync(block, valuesByReference);

            _context = _context.BeforeEnd();
            await _builder.EndWriteBlockAsync(_context);

            _context = _context.AfterEnd();
        }
        private async Task InstructPageRenderingAsync(XElement page, Dictionary <string, string> valuesByReference)
        {
            AssertElementName(page, "page");

            _context = _context.BeforeBegin(page.Name.LocalName, hasContent: page.HasContent());
            await _builder.BeginWritePageAsync(_context);

            _context = _context.AfterBegin();

            await TraverseContainerElementAsync(page, valuesByReference);

            _context = _context.BeforeEnd();
            await _builder.EndWritePageAsync(_context);

            _context = _context.AfterEnd();
        }
        public async Task InstructRenderingAsync(string markup, DocumentRenderModel model, IDocumentBuilderV1 builder)
        {
            if (_context != null)
            {
                throw new InvalidOperationException();
            }

            _context = new DocumentInstructionContextV1();
            _builder = builder;
            _model   = model;
            _listItemIndexContinueOffsetByNestingLevel = new Dictionary <int, int>();

            XDocument document = null;

            using (var sr = new StringReader(markup))
            {
                document = XDocument.Load(sr);
            }

            var root = document.Root;

            var valuesByReference = _model.Items.ToDictionary(i => i.Reference, i => i.Value);

            _context = _context.BeforeBegin(document.Root.Name.LocalName, hasContent: document.Root.HasContent());
            await builder.BeginWriteDocumentAsync(model, _context);

            _context = _context.AfterBegin();

            foreach (var page in root.Elements())
            {
                await WriteConditionalElementAsync(page, valuesByReference, async() =>
                {
                    await InstructPageRenderingAsync(page, valuesByReference);
                });
            }

            _context = _context.BeforeEnd();
            await builder.EndWriteDocumentAsync(_context);

            _context = _context.AfterEnd();
        }
        private async Task InstructWriteTextAsync(string text, string reference = null)
        {
            if (Regex.Match(text, @"^\s").Success)
            {
                if (_context.IsFirstChild || _context.IsPreviousSiblingBlockLike)
                {
                    text = text.TrimStart();
                }
                else
                {
                    text = " " + text.TrimStart();
                }
            }

            if (Regex.Match(text, @"\s$").Success)
            {
                text = text.TrimEnd() + " ";
            }

            _context = _context.BeforeBegin("text", hasContent: false);
            await _builder.WriteTextAsync(text, _includeMetadata?reference : null, _context);

            _context = _context.AfterBegin().BeforeEnd().AfterEnd();
        }