private void RenderList(ListLayout layout, IContentContainer container) { ++_numberingLevel; s.ListStyle listStyle = (s.ListStyle)layout.Style; // Translate the bullet text from design syntax to Word syntax. The // design specification can include formatting of its own, unrelated // to the list style. The easiest way to interpret such formatting // is to create a text layout from the bullet text, which will // separate the formatting from the text. If the bullet includes // formatting then the new text layout will start with a paragraph // verse, which we'll ignore. And it could contain any // number of other verses, but a numbering definition in Word supports // only a single run, so we just concatenate the text from all the // verses, and apply the first verse format we find. Note that our // report design language doesn't support including lower level numbers // in the number, such as 3x in 1, 2, 3a, 3b, 3c, 4, 5 etc. s.BulletStyle bulletStyle = listStyle.BulletStyle; string bulletText = bulletStyle.BulletText .Replace("%", "%%") .Replace(ListItemLayout.BulletNumberProperty, $"%{_numberingLevel+1}"); TextFormat bulletFormat = new TextFormat(bulletStyle.Font, bulletStyle.Color); TextBlock block = new TextBlock(bulletText, bulletFormat, _generator); bulletText = block.Verses.Select(v => v.Text).Aggregate((c, n) => c + n); bulletFormat = block.Verses.Select(v => v.Format).Where(f => f != null).FirstOrDefault(); int numberingStyleId = _document.AddNumberingStyle(bulletStyle, _numberingLevel, bulletText); // Create a numbering instance based on the numbering style, and add it // to the paragraph. As far as I can make out, in a sequence of numbered // paragraphs every paragraph refers to the one numbering instance. // Any number of such sequences can get their numbering from the one // underlying abstract definition, but each sequence would have its own // instance. Instances are stored in the numbering part along with the // abstract definitions. // // We have to store abstract definitions in a dictionary in the document // because we need random access to them so that different layouts in the // design can share a single definition. But we can store instances in a // simple stack in this class because each one is needed only for the // current list. int numberingInstanceId = _document.AddNumberingInstance(numberingStyleId); _numberingInstances.Push(numberingInstanceId); // Lists in the design can be nested - that's how we do indented list // levels. But in Word each item must be its own paragraph, and the // items together form the list by being contiguous paragraphs with // a common numbering style. That is, Word doesn't have lists, but // rather just has numbered paragraphs. foreach (Layout sublayout in layout.SubLayouts) { Render(sublayout, container); } _numberingInstances.Pop(); --_numberingLevel; }
private void RenderListItem(ListItemLayout layout, IContentContainer container) { // A list item, which is based on a single design layout, can // include any number of design paragraphs introduced by embedded // HTML <p> tags. To render these as a single item in the Word // list, they must all be rendered as a single Word paragraph. // We achieve this by concatenating all the design paragraphs // together, with double line break separators. The first line // break starts a new line, and the second inserts some whitespace. // // A nested list is introduced by a group layout, which is a single // item in the current list and which contains its own list. // // We only support text content in list items - no photos. // // So the list item layout's content sublayout is always either // a text layout or a group layout. And the list item is always // a single Word paragraph. // The item style is to be found in the item's list's style s.ListStyle listStyle = (s.ListStyle)layout.Style; s.TextStyle itemStyle = listStyle.ItemStyle; IParagraph paragraph = container.AddParagraph(itemStyle, layout.TrackingInfo); int numberingInstanceId = _numberingInstances.Peek(); // stack guaranteed not to be empty paragraph.SetNumbering(numberingInstanceId, _numberingLevel); switch (layout.ContentLayout.LayoutType) { case LayoutType.Text: { TextLayout contentLayout = (TextLayout)layout.ContentLayout; List <Verse> verses = contentLayout.GetFormattedText(); if (verses == null) { break; } if (verses.Count == 0) { break; } foreach (Verse verse in verses) { if (verse is ParagraphVerse) { // Two line breaks to look like a new paragraph without actually // being a new Word paragraph. But don't do this if it's the first // verse. if (verse == verses[0]) { continue; } paragraph.AddLineBreak(); paragraph.AddLineBreak(); } else if (verse is LineBreakVerse) { paragraph.AddLineBreak(); } else { paragraph.AddRun(verse.Text, verse.Format.Font, verse.Format.Color); } } break; } case LayoutType.Group: { foreach (Layout sublayout in layout.ContentLayout.SubLayouts) { Render(sublayout, container); } break; } default: { break; } } }