Как сгенерировать html код для вложенных списков
В общем ситуация следующая я пытаюсь распарcить docx файл и представить его в виде html у меня возникли проблемы со вложенными списками. В процессе обработки параграфов я должен понять является ли параграф списком если он таким является, то я открываю тег <ul>
Это класс не из OpenXML, это объявленный мною класс
public class Paragraph
{
public MainDocumentPart DocumentPart { get; set; }
public OpenXmlElement Element { get; set; }
public bool IsList { get; set; }
public List? List { get; set; }
}
public class List
{
public int Level { get; set; }
public ListType ListType { get; set;}
}
У списка имеется уровень и тип(маркированный или нумерованный)
Обработчик параграфов
public void ParagraphHandle(Elements.Paragraph paragraph, StringBuilder text)
{
var docPart = paragraph.DocumentPart;
var element = paragraph.Element;
var images = GetImages(docPart, element);
if (images.Count > 0)
{
foreach (var image in images)
{
if (image.Id != null)
{
string filePath = _saveResources.SaveImage(image);
_htmlGenerator.GenerateImage(filePath, text);
}
}
return;
}
var paragraphProperties = element.GetFirstChild<DocumentFormat.OpenXml.Wordprocessing.ParagraphProperties>();
var numberingProperties = paragraphProperties?.GetFirstChild<NumberingProperties>();
if (numberingProperties != null)
{
var numberingId = numberingProperties.GetFirstChild<NumberingId>()?.Val?.Value;
if (numberingId != null && !paragraph.IsList) // новый список
{
var instance = paragraph.DocumentPart.NumberingDefinitionsPart?.Numbering
.Descendants<NumberingInstance>()
.SingleOrDefault(x => x.NumberID?.Value == numberingId);
string? attributeValue = instance?.Descendants<AbstractNumId>().FirstOrDefault()
?.GetAttribute("val", "http://schemas.openxmlformats.org/wordprocessingml/2006/main").Value;
var level = paragraph.DocumentPart.NumberingDefinitionsPart?.Numbering.Descendants<Level>()
.FirstOrDefault(x => ((AbstractNum)x.Parent!).AbstractNumberId == attributeValue);
paragraph.List = new List();
paragraph.IsList = true;
switch (level?.NumberingFormat?.Val?.Value.ToString())
{
case Constants.ListType.Bullet:
paragraph.List.ListType = ListType.Marked;
break;
default:
paragraph.List.ListType = ListType.Numbered;
break;
}
switch (paragraph.List.ListType)
{
case ListType.Marked:
text.AppendLine("<ul>");
break;
case ListType.Numbered:
text.AppendLine("<ol>");
break;
}
_htmlGenerator.GenerateList(paragraph, text);
}
else //текущий список
{
_htmlGenerator.GenerateList(paragraph, text);
}
}
else
{
if (paragraph.IsList)
{
switch (paragraph.List?.ListType)
{
case ListType.Marked:
text.AppendLine("</ul>");
break;
case ListType.Numbered:
text.AppendLine("</ol>");
break;
}
paragraph.IsList = false;
}
_htmlGenerator.GenerateParagraph(element.InnerText, text);
}
}
Генерация html кода для списков
public void GenerateList(Elements.Paragraph paragraph, StringBuilder text)
{
int? level = paragraph.Element.GetFirstChild<ParagraphProperties>()?
.GetFirstChild<NumberingProperties>()?.GetFirstChild<NumberingLevelReference>()?.Val?.Value;
if (level != null)
{
var list = paragraph.List;
if (level > list?.Level)
{
switch (paragraph.List?.ListType)
{
case ListType.Marked:
text.AppendLine("<ul>");
break;
case ListType.Numbered:
text.AppendLine("<ol>");
break;
}
list.Level = level.Value;
}
if (level < list?.Level)
{
switch (paragraph.List?.ListType)
{
case ListType.Marked:
text.AppendLine("</ul>");
break;
case ListType.Numbered:
text.AppendLine("</ol>");
break;
}
}
}
text.AppendLine($"<li>{paragraph.Element.InnerText}</li>");
}
И вот ещё запилил такой костыль если список один в документе, чтобы закрыть тег <ul>
public void Handle(MainDocumentPart mainDocumentPart, StringBuilder text)
{
var body = mainDocumentPart.Document.Body;
if(body == null)
return;
var paragraph = new Elements.Paragraph
{
DocumentPart = mainDocumentPart,
IsList = false
};
foreach (OpenXmlElement element in body.ChildElements)
{
string type = element.GetType().ToString();
paragraph.Element = element;
switch (type)
{
case ElementType.Paragraph:
_elementHandler.ParagraphHandle(paragraph, text);
continue;
case ElementType.Table:
_elementHandler.TableHandle((Table)element, text);
continue;
}
}
if (paragraph.IsList)
{
switch (paragraph.List?.ListType)
{
case Elements.Enums.ListType.Marked:
text.AppendLine("</ul>");
break;
case Elements.Enums.ListType.Numbered:
text.AppendLine("</ol>");
break;
}
}
}
Ответы (1 шт):
Просто покажу, как создавать вложенные списки в HTML с помощью HtmlAgilityPack. Я сам начинал со StringBuilder для генерации HTML, и это были очень суровые времена. Используйте для этого специально предназначенные инструменты, и проблема с синтаксисом HTML решится сама собой.
static void Main(string[] args)
{
HtmlDocument doc = new HtmlDocument();
var ul = doc.CreateElement("ul");
for (int i = 0; i < 2; i++)
{
var li = doc.CreateElement("li");
li.InnerHtml = "Первый";
ul.AppendChild(li);
}
var ul2 = doc.CreateElement("ul");
for (int i = 0; i < 3; i++)
{
var li = doc.CreateElement("li");
li.InnerHtml = "Второй";
ul2.AppendChild(li);
}
ul.LastChild.AppendChild(ul2);
var ul3 = doc.CreateElement("ul");
for (int i = 0; i < 3; i++)
{
var li = doc.CreateElement("li");
li.InnerHtml = "Третий";
ul3.AppendChild(li);
}
ul2.LastChild.AppendChild(ul3);
doc.DocumentNode.AppendChild(ul);
doc.DocumentNode.AppendChild(doc.CreateElement("br"));
for (int i = 0; i < 3; i++)
{
var p = doc.CreateElement("p");
p.InnerHtml = "Текст";
doc.DocumentNode.AppendChild(p);
}
doc.DocumentNode.AppendChild(doc.CreateElement("br"));
Console.WriteLine(doc.DocumentNode.InnerHtml);
}
Вывод в консоль
<ul><li>Первый</li><li>Первый<ul><li>Второй</li><li>Второй</li><li>Второй<ul><li>Третий</li><li>Третий</li><li>Третий</li></ul></li></ul></li></ul><br><p>Текст</p><p>Текст</p><p>Текст</p><br>
Выходной HTML не отформатирован красиво, но если это минус, то вы можете выполнить постобработку с помощью другой библиотеки, например HtmlTidy, наверняка есть и другие.
<ul>
<li>Первый</li>
<li>Первый
<ul>
<li>Второй</li>
<li>Второй</li>
<li>Второй
<ul>
<li>Третий</li>
<li>Третий</li>
<li>Третий</li>
</ul>
</li>
</ul>
</li>
</ul>
<br>
<p>Текст</p>
<p>Текст</p>
<p>Текст</p>
<br>
То есть по W3C стандартам дочерний список должен быть внутри li старшего списка, а не просто быть вложенным, как у вас. При чем, не внутри пустого li, а следовать за контентом этого элемента. Логически подпункты списка обычно относятся к старшему пункту, собственно эта особенность и включена в стандарт.