Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Indent lists in notes #515

Merged
merged 1 commit into from
Mar 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions tools/MarkdownConverter.Tests/MarkdownSourceConverterTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ public class MarkdownSourceConverterTests
[InlineData("note")]
[InlineData("code-block-in-list")]
[InlineData("table-in-list")]
public void SingleResourceConversion(string name)
[InlineData("list-in-note", true)]
public void SingleResourceConversion(string name, bool includeNumbering = false)
{
var reporter = new Reporter(TextWriter.Null);
var expectedXml = ReadResource($"{name}.xml");
Expand All @@ -39,8 +40,11 @@ public void SingleResourceConversion(string name)
// Gather all the paragraphs together, but remove all namespaces aliases so our test documents can be simpler.
// (While a single declaration of the namespace in the root element works as a default for element names,
// it doesn't help with attribute names.)
// We optionally include the numbering details - this is basically for tests where list indentation is important.
var paragraphs = converter.Paragraphs.ToList();
XDocument actualXDocument = XDocument.Parse($@"<doc>{string.Join("\r\n", paragraphs.Select(p => p.OuterXml))}</doc>");
string? numberingXml = includeNumbering ? resultDoc.MainDocumentPart?.NumberingDefinitionsPart?.Numbering?.OuterXml : null;
string paragraphsXml = string.Join("\r\n", paragraphs.Select(p => p.OuterXml));
XDocument actualXDocument = XDocument.Parse($@"<doc>{numberingXml}{paragraphsXml}</doc>");
// Remove attributes
foreach (var element in actualXDocument.Root!.Descendants())
{
Expand Down
8 changes: 8 additions & 0 deletions tools/MarkdownConverter.Tests/list-in-note.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# 1 Heading

> *Note:* this is a note.
>
> - List level 1
> - List level 2
>
> *end note*
107 changes: 107 additions & 0 deletions tools/MarkdownConverter.Tests/list-in-note.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
<doc>
<numbering>
<abstractNum abstractNumId="1">
<multiLevelType val="multilevel" />
<lvl ilvl="0">
<start val="1" />
<numFmt val="bullet" />
<lvlText val="·" />
<pPr>
<ind left="1080" hanging="360" />
</pPr>
<rPr>
<rFonts hint="default" ascii="Symbol" hAnsi="Symbol" eastAsia="Times new Roman" cs="Times new Roman" />
</rPr>
</lvl>
<lvl ilvl="1">
<start val="1" />
<numFmt val="bullet" />
<lvlText val="o" />
<pPr>
<ind left="1440" hanging="360" />
</pPr>
<rPr>
<rFonts hint="default" ascii="Courier New" hAnsi="Courier New" cs="Courier New" />
</rPr>
</lvl>
<lvl ilvl="2">
<start val="1" />
<numFmt val="lowerRoman" />
<lvlText val="%3." />
<pPr>
<ind left="1800" hanging="360" />
</pPr>
</lvl>
<lvl ilvl="3">
<start val="1" />
<numFmt val="lowerRoman" />
<lvlText val="%4." />
<pPr>
<ind left="2160" hanging="360" />
</pPr>
</lvl>
</abstractNum>
<num numId="1">
<abstractNumId val="1" />
</num>
</numbering>
<p>
<pPr>
<pStyle val="Heading1" />
</pPr>
<bookmarkStart name="_Toc00001" id="1" />
<r>
<t space="preserve">Heading</t>
</r>
<bookmarkEnd id="1" />
</p>
<p>
<pPr>
<ind left="540" />
</pPr>
<r>
<rPr>
<i />
</rPr>
<t space="preserve">Note:</t>
</r>
<r>
<t space="preserve"> this is a note.</t>
</r>
</p>
<p>
<pPr>
<numPr>
<pStyle val="ListParagraph" />
<ilvl val="0" />
<numId val="1" />
</numPr>
</pPr>
<r>
<t space="preserve">List level 1</t>
</r>
</p>
<p>
<pPr>
<numPr>
<pStyle val="ListParagraph" />
<ilvl val="1" />
<numId val="1" />
</numPr>
</pPr>
<r>
<t space="preserve">List level 2</t>
</r>
</p>
<p>
<pPr>
<ind left="540" />
</pPr>
<r>
<rPr>
<i />
</rPr>
<t space="preserve">end note</t>
</r>
</p>
</doc>
60 changes: 45 additions & 15 deletions tools/MarkdownConverter/Converter/MarkdownSourceConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ public class MarkdownSourceConverter
/// </summary>
public const int MaximumCodeLineLength = 95;

private const int InitialIndentation = 540;
private const int ListLevelIndentation = 360;
private const int TableIndentation = 360;

private static readonly Dictionary<char, char> SubscriptUnicodeToAscii = new Dictionary<char, char>
{
{ '\u1d62', 'i' },
Expand Down Expand Up @@ -130,6 +134,11 @@ IEnumerable<OpenXmlCompositeElement> Paragraph2Paragraphs(MarkdownParagraph md)

else if (md.IsQuotedBlock)
{
// Keep track of which list numbering schemes we've already indented.
// Lists are flattened into multiple paragraphs, but all paragraphs within one list
// keep the same numbering scheme, and we only want to increase the indentation level once.
var indentedLists = new HashSet<int>();

var mdq = md as MarkdownParagraph.QuotedBlock;
// TODO: Actually make this a block quote.
// We're now indenting, which is a start... a proper block would be nicer though.
Expand All @@ -138,15 +147,39 @@ IEnumerable<OpenXmlCompositeElement> Paragraph2Paragraphs(MarkdownParagraph md)
if (element is Paragraph paragraph)
{
paragraph.ParagraphProperties ??= new ParagraphProperties();
paragraph.ParagraphProperties.Indentation = new Indentation { Left = "540" };

// Indentation in lists is controlled by numbering properties.
// Each list creates its own numbering, with a set of properties for each numbering level.
// If there's a list within a note, we need to increase the indentation of each numbering level.
if (paragraph.ParagraphProperties.NumberingProperties?.NumberingId?.Val?.Value is int numberingId)
{
if (indentedLists.Add(numberingId))
{
var numbering = wordDocument.MainDocumentPart.NumberingDefinitionsPart.Numbering.OfType<NumberingInstance>().First(ni => ni.NumberID.Value == numberingId);
var abstractNumberingId = numbering.AbstractNumId.Val;
var abstractNumbering = wordDocument.MainDocumentPart.NumberingDefinitionsPart.Numbering.OfType<AbstractNum>().FirstOrDefault(ani => ani.AbstractNumberId.Value == abstractNumberingId);
foreach (var level in abstractNumbering.OfType<Level>())
{
var paragraphProperties = level.GetFirstChild<ParagraphProperties>();
int indentation = int.Parse(paragraphProperties.Indentation.Left.Value);
paragraphProperties.Indentation.Left.Value = (indentation + InitialIndentation).ToString();
}
}
}
else
{
paragraph.ParagraphProperties.Indentation = new Indentation { Left = InitialIndentation.ToString() };
}
yield return paragraph;
}
else if (element is Table table)
{
if (table.ElementAt(0) is TableProperties tableProperties)
{
tableProperties.TableIndentation ??= new TableIndentation();
tableProperties.TableIndentation.Width = 540;
// TODO: This will be incorrect if we ever have a table in a list in a note.
// Let's just try not to do that.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LOLZ

tableProperties.TableIndentation.Width = InitialIndentation;
yield return table;
}
else
Expand Down Expand Up @@ -182,8 +215,7 @@ IEnumerable<OpenXmlCompositeElement> Paragraph2Paragraphs(MarkdownParagraph md)
numberingPart.Numbering = new Numbering();
}

Func<int, bool, Level> createLevel;
createLevel = (level, isOrdered) =>
Func<int, bool, Level> createLevel = (level, isOrdered) =>
{
var numformat = NumberFormatValues.Bullet;
var levelText = new[] { "·", "o", "·", "o" }[level];
Expand All @@ -195,7 +227,7 @@ IEnumerable<OpenXmlCompositeElement> Paragraph2Paragraphs(MarkdownParagraph md)
r.Append(new StartNumberingValue { Val = 1 });
r.Append(new NumberingFormat { Val = numformat });
r.Append(new LevelText { Val = levelText });
r.Append(new ParagraphProperties(new Indentation { Left = (540 + 360 * level).ToString(), Hanging = "360" }));
r.Append(new ParagraphProperties(new Indentation { Left = (InitialIndentation + ListLevelIndentation * level).ToString(), Hanging = ListLevelIndentation.ToString() }));
if (levelText == "·")
{
r.Append(new NumberingSymbolRunProperties(new RunFonts { Hint = FontTypeHintValues.Default, Ascii = "Symbol", HighAnsi = "Symbol", EastAsia = "Times new Roman", ComplexScript = "Times new Roman" }));
Expand Down Expand Up @@ -224,10 +256,8 @@ IEnumerable<OpenXmlCompositeElement> Paragraph2Paragraphs(MarkdownParagraph md)
numberingPart.Numbering.AppendChild(numInstance);

// We'll also figure out the indentation(for the benefit of those paragraphs that should be
// indendent with the list but aren't numbered). I'm not sure what the indent comes from.
// in the docx, each AbstractNum that I created has an indent for each of its levels,
// defaulted at 900, 1260, 1620, ... but I can't see where in the above code that's created?
Func<int, string> calcIndent = level => (540 + level * 360).ToString();
// indented with the list but aren't numbered). The indentation is generated by the createLevel delegate.
Func<int, string> calcIndent = level => (InitialIndentation + level * ListLevelIndentation).ToString();

foreach (var item in flat)
{
Expand Down Expand Up @@ -1067,7 +1097,7 @@ Run CreateRun(XNode node)

internal static IEnumerable<OpenXmlCompositeElement> CreateMultiplicationTable()
{
Table table = CreateTable(indentation: 900, width: 8000);
Table table = CreateTable(indentation: TableIndentation + InitialIndentation, width: 8000);
table.Append(CreateTableRow(Empty, PlusY, MinusY, PlusZero, MinusZero, PlusInfinity, MinusInfinity, NaN));
table.Append(CreateTableRow(PlusX, PlusZ, MinusZ, PlusZero, MinusZero, PlusInfinity, MinusInfinity, NaN));
table.Append(CreateTableRow(MinusX, MinusZ, PlusZ, MinusZero, PlusZero, MinusInfinity, PlusInfinity, NaN));
Expand All @@ -1081,7 +1111,7 @@ internal static IEnumerable<OpenXmlCompositeElement> CreateMultiplicationTable()

internal static IEnumerable<OpenXmlCompositeElement> CreateDivisionTable()
{
Table table = CreateTable(indentation: 900, width: 8000);
Table table = CreateTable(indentation: TableIndentation + InitialIndentation, width: 8000);
table.Append(CreateTableRow(Empty, PlusY, MinusY, PlusZero, MinusZero, PlusInfinity, MinusInfinity, NaN));
table.Append(CreateTableRow(PlusX, PlusZ, MinusZ, PlusInfinity, MinusInfinity, PlusZero, MinusZero, NaN));
table.Append(CreateTableRow(MinusX, MinusZ, PlusZ, MinusInfinity, PlusInfinity, MinusZero, PlusZero, NaN));
Expand All @@ -1095,7 +1125,7 @@ internal static IEnumerable<OpenXmlCompositeElement> CreateDivisionTable()

internal static IEnumerable<OpenXmlCompositeElement> CreateRemainderTable()
{
Table table = CreateTable(indentation: 900, width: 8000);
Table table = CreateTable(indentation: TableIndentation + InitialIndentation, width: 8000);
table.Append(CreateTableRow(Empty, PlusY, MinusY, PlusZero, MinusZero, PlusInfinity, MinusInfinity, NaN));
table.Append(CreateTableRow(PlusX, PlusZ, PlusZ, NaN, NaN, PlusX, PlusX, NaN));
table.Append(CreateTableRow(MinusX, MinusZ, MinusZ, NaN, NaN, MinusX, MinusX, NaN));
Expand All @@ -1109,7 +1139,7 @@ internal static IEnumerable<OpenXmlCompositeElement> CreateRemainderTable()

internal static IEnumerable<OpenXmlCompositeElement> CreateAdditionTable()
{
Table table = CreateTable(indentation: 900, width: 8000);
Table table = CreateTable(indentation: TableIndentation + InitialIndentation, width: 8000);
table.Append(CreateTableRow(Empty, Y, PlusZero, MinusZero, PlusInfinity, MinusInfinity, NaN));
table.Append(CreateTableRow(X, Z, X, X, PlusInfinity, MinusInfinity, NaN));
table.Append(CreateTableRow(PlusZero, Y, PlusZero, PlusZero, PlusInfinity, MinusInfinity, NaN));
Expand All @@ -1122,7 +1152,7 @@ internal static IEnumerable<OpenXmlCompositeElement> CreateAdditionTable()

internal static IEnumerable<OpenXmlCompositeElement> CreateSubtractionTable()
{
Table table = CreateTable(indentation: 900, width: 8000);
Table table = CreateTable(indentation: TableIndentation + InitialIndentation, width: 8000);
table.Append(CreateTableRow(Empty, Y, PlusZero, MinusZero, PlusInfinity, MinusInfinity, NaN));
table.Append(CreateTableRow(X, Z, X, X, MinusInfinity, PlusInfinity, NaN));
table.Append(CreateTableRow(PlusZero, MinusY, PlusZero, PlusZero, MinusInfinity, PlusInfinity, NaN));
Expand All @@ -1142,7 +1172,7 @@ internal static IEnumerable<OpenXmlCompositeElement> CreateTestTable()

private static TableRow CreateTableRow(params TableCell[] cells) => new TableRow(cells);

internal static Table CreateTable(int indentation = 360, int? width = null)
internal static Table CreateTable(int indentation = TableIndentation, int? width = null)
{
var props = new TableProperties
{
Expand Down