Files
csharpcode/omegapro/PowerTools/Classes/ListItemRetriever.cs
2025-08-02 05:20:17 +07:00

634 lines
27 KiB
C#

/***************************************************************************
Copyright (c) Microsoft Corporation 2010.
This code is licensed using the Microsoft Public License (Ms-PL). The text of the license
can be found here:
http://www.microsoft.com/resources/sharedsource/licensingbasics/publiclicense.mspx
***************************************************************************/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using DocumentFormat.OpenXml.Packaging;
namespace OpenXmlPowerTools
{
public partial class WmlDocument : OpenXmlPowerToolsDocument
{
public string RetrieveListItem(XElement paragraph, string bulletReplacementString)
{
return ListItemRetriever.RetrieveListItem(this, paragraph, bulletReplacementString);
}
}
public class ListItemRetriever
{
private class ListItemInfo
{
public bool IsListItem;
public XElement Lvl;
public int? Start;
public int? AbstractNumId;
public ListItemInfo(bool isListItem)
{
IsListItem = isListItem;
}
}
private class LevelNumbers
{
public int[] LevelNumbersArray;
}
private static ListItemInfo GetListItemInfoByNumIdAndIlvl(XDocument numbering,
XDocument styles, int numId, int ilvl)
{
if (numId == 0)
return new ListItemInfo(false);
ListItemInfo listItemInfo = new ListItemInfo(true);
XElement num = numbering.Root.Elements(W.num)
.Where(e => (int)e.Attribute(W.numId) == numId).FirstOrDefault();
if (num == null)
return new ListItemInfo(false);
listItemInfo.AbstractNumId = (int?)num.Elements(W.abstractNumId)
.Attributes(W.val).FirstOrDefault();
XElement lvlOverride = num.Elements(W.lvlOverride)
.Where(e => (int)e.Attribute(W.ilvl) == ilvl).FirstOrDefault();
// If there is a w:lvlOverride element, and if the w:lvlOverride contains a
// w:lvl element, then return it. Otherwise, go look in the abstract numbering
// definition.
if (lvlOverride != null)
{
// Get the startOverride, if there is one.
listItemInfo.Start = (int?)num.Elements(W.lvlOverride)
.Where(o => (int)o.Attribute(W.ilvl) == ilvl).Elements(W.startOverride)
.Attributes(W.val).FirstOrDefault();
listItemInfo.Lvl = lvlOverride.Element(W.lvl);
if (listItemInfo.Lvl != null)
{
if (listItemInfo.Start == null)
listItemInfo.Start = (int?)listItemInfo.Lvl.Elements(W.start)
.Attributes(W.val).FirstOrDefault();
return listItemInfo;
}
}
int? a = listItemInfo.AbstractNumId;
XElement abstractNum = numbering.Root.Elements(W.abstractNum)
.Where(e => (int)e.Attribute(W.abstractNumId) == a).FirstOrDefault();
string numStyleLink = (string)abstractNum.Elements(W.numStyleLink)
.Attributes(W.val).FirstOrDefault();
if (numStyleLink != null)
{
XElement style = styles.Root.Elements(W.style)
.Where(e => (string)e.Attribute(W.styleId) == numStyleLink)
.FirstOrDefault();
XElement numPr = style.Elements(W.pPr).Elements(W.numPr).FirstOrDefault();
int lNumId = (int)numPr.Elements(W.numId).Attributes(W.val)
.FirstOrDefault();
return GetListItemInfoByNumIdAndIlvl(numbering, styles, lNumId, ilvl);
}
for (int l = ilvl; l >= 0; --l)
{
listItemInfo.Lvl = abstractNum.Elements(W.lvl)
.Where(e => (int)e.Attribute(W.ilvl) == l).FirstOrDefault();
if (listItemInfo.Lvl == null)
continue;
if (listItemInfo.Start == null)
listItemInfo.Start = (int?)listItemInfo.Lvl.Elements(W.start)
.Attributes(W.val).FirstOrDefault();
return listItemInfo;
}
return new ListItemInfo(false);
}
private static ListItemInfo GetListItemInfoByNumIdAndStyleId(XDocument numbering,
XDocument styles, int numId, string paragraphStyle)
{
// If you have to find the w:lvl by style id, then we can't find it in the
// w:lvlOverride, as that requires that you have determined the level already.
ListItemInfo listItemInfo = new ListItemInfo(true);
XElement num = numbering.Root.Elements(W.num)
.Where(e => (int)e.Attribute(W.numId) == numId).FirstOrDefault();
listItemInfo.AbstractNumId = (int)num.Elements(W.abstractNumId)
.Attributes(W.val).FirstOrDefault();
int? a = listItemInfo.AbstractNumId;
XElement abstractNum = numbering.Root.Elements(W.abstractNum)
.Where(e => (int)e.Attribute(W.abstractNumId) == a).FirstOrDefault();
string numStyleLink = (string)abstractNum.Element(W.numStyleLink)
.Attributes(W.val).FirstOrDefault();
if (numStyleLink != null)
{
XElement style = styles.Root.Elements(W.style)
.Where(e => (string)e.Attribute(W.styleId) == numStyleLink)
.FirstOrDefault();
XElement numPr = style.Elements(W.pPr).Elements(W.numPr).FirstOrDefault();
int lNumId = (int)numPr.Elements(W.numId).Attributes(W.val).FirstOrDefault();
return GetListItemInfoByNumIdAndStyleId(numbering, styles, lNumId,
paragraphStyle);
}
listItemInfo.Lvl = abstractNum.Elements(W.lvl)
.Where(e => (string)e.Element(W.pStyle) == paragraphStyle).FirstOrDefault();
listItemInfo.Start = (int?)listItemInfo.Lvl.Elements(W.start).Attributes(W.val)
.FirstOrDefault();
return listItemInfo;
}
private static ListItemInfo GetListItemInfo(XDocument numbering, XDocument styles,
XElement paragraph)
{
// The following is an optimization - only determine ListItemInfo once for a
// paragraph.
ListItemInfo listItemInfo = paragraph.Annotation<ListItemInfo>();
if (listItemInfo != null)
return listItemInfo;
XElement paragraphNumberingProperties = paragraph.Elements(W.pPr)
.Elements(W.numPr).FirstOrDefault();
string paragraphStyle = (string)paragraph.Elements(W.pPr).Elements(W.pStyle)
.Attributes(W.val).FirstOrDefault();
if (paragraphNumberingProperties != null &&
paragraphNumberingProperties.Element(W.numId) != null)
{
// Paragraph numbering properties must contain a numId.
int numId = (int)paragraphNumberingProperties.Elements(W.numId)
.Attributes(W.val).FirstOrDefault();
int? ilvl = (int?)paragraphNumberingProperties.Elements(W.ilvl)
.Attributes(W.val).FirstOrDefault();
if (ilvl != null)
{
listItemInfo = GetListItemInfoByNumIdAndIlvl(numbering, styles, numId,
(int)ilvl);
paragraph.AddAnnotation(listItemInfo);
return listItemInfo;
}
if (paragraphStyle != null)
{
listItemInfo = GetListItemInfoByNumIdAndStyleId(numbering, styles,
numId, paragraphStyle);
paragraph.AddAnnotation(listItemInfo);
return listItemInfo;
}
listItemInfo = new ListItemInfo(false);
paragraph.AddAnnotation(listItemInfo);
return listItemInfo;
}
if (paragraphStyle != null)
{
XElement style = styles.Root.Elements(W.style).Where(s =>
(string)s.Attribute(W.type) == "paragraph" &&
(string)s.Attribute(W.styleId) == paragraphStyle).FirstOrDefault();
if (style != null)
{
XElement styleNumberingProperties = style.Elements(W.pPr)
.Elements(W.numPr).FirstOrDefault();
if (styleNumberingProperties != null &&
styleNumberingProperties.Element(W.numId) != null)
{
int numId = (int)styleNumberingProperties.Elements(W.numId)
.Attributes(W.val).FirstOrDefault();
int? ilvl = (int?)styleNumberingProperties.Elements(W.ilvl)
.Attributes(W.val).FirstOrDefault();
if (ilvl == null)
ilvl = 0;
listItemInfo = GetListItemInfoByNumIdAndIlvl(numbering, styles,
numId, (int)ilvl);
paragraph.AddAnnotation(listItemInfo);
return listItemInfo;
}
}
}
listItemInfo = new ListItemInfo(false);
paragraph.AddAnnotation(listItemInfo);
return listItemInfo;
}
private static IEnumerable<LevelNumbers> ParagraphsToConsiderWhenCounting(
XDocument numbering, XDocument styles, XElement paragraph, int levelNumber)
{
ListItemInfo listItemInfo = GetListItemInfo(numbering, styles, paragraph);
int? lvlRestart = (int?)listItemInfo.Lvl.Elements(W.lvlRestart)
.Attributes(W.val).FirstOrDefault();
int paragraphLevel = (int)listItemInfo.Lvl.Attribute(W.ilvl);
IEnumerable<XElement> paragraphsBeforeSelf = paragraph
.ElementsBeforeSelfReverseDocumentOrder().Where(e => e.Name == W.p);
foreach (var p in paragraphsBeforeSelf)
{
ListItemInfo pListItemInfo = GetListItemInfo(numbering, styles, p);
if (!pListItemInfo.IsListItem ||
pListItemInfo.AbstractNumId != listItemInfo.AbstractNumId)
continue;
LevelNumbers pLevelNumbers = p.Annotation<LevelNumbers>();
int pLevel = (int)pListItemInfo.Lvl.Attribute(W.ilvl);
if (pLevel > levelNumber)
yield return pLevelNumbers;
if (lvlRestart == null)
{
if (pLevel < levelNumber)
yield break;
}
else
{
if (pLevel < levelNumber && pLevel > lvlRestart - 1)
continue;
else if (pLevel < levelNumber)
yield break;
}
yield return pLevelNumbers;
}
}
private static int GetLevelNumberForLevel(XDocument numbering, XDocument styles,
XElement paragraph, int level)
{
ListItemInfo listItemInfo = GetListItemInfo(numbering, styles, paragraph);
int paragraphLevel = (int)listItemInfo.Lvl.Attribute(W.ilvl);
var paragraphsToConsider = ParagraphsToConsiderWhenCounting(numbering, styles,
paragraph, level)
.Select(o => o.LevelNumbersArray.Take(paragraphLevel + 1)
.Select(z => z.ToString() + ".").StringConcatenate())
.GroupBy(o => o);
int levelNumberForLevel = paragraphsToConsider.Count();
return levelNumberForLevel;
}
private static int[] GetLevelNumbers(XDocument numbering, XDocument styles,
XElement paragraph)
{
IEnumerable<XElement> paragraphsBeforeSelf = paragraph
.ElementsBeforeSelfReverseDocumentOrder().Where(e => e.Name == W.p);
int level;
ListItemInfo listItemInfo = GetListItemInfo(numbering, styles, paragraph);
level = (int)listItemInfo.Lvl.Attribute(W.ilvl);
List<int> levelNumbers = new List<int>();
for (int indentationLevel = 0; indentationLevel <= level; ++indentationLevel)
{
XElement currentIndentLvl = GetRelatedLevel(listItemInfo.Lvl,
indentationLevel);
int? start = (int?)currentIndentLvl.Elements(W.start).Attributes(W.val)
.FirstOrDefault();
if (start == null)
start = 1;
XElement paragraphWithSameAbstractNumId = paragraphsBeforeSelf
.FirstOrDefault(p =>
{
ListItemInfo pListItemInfo = GetListItemInfo(numbering, styles, p);
return pListItemInfo.IsListItem &&
pListItemInfo.AbstractNumId == listItemInfo.AbstractNumId;
});
if (paragraphWithSameAbstractNumId != null)
{
LevelNumbers pLevelNumbers = paragraphWithSameAbstractNumId
.Annotation<LevelNumbers>();
if (pLevelNumbers.LevelNumbersArray.Length > indentationLevel)
{
if (indentationLevel == level)
levelNumbers.Add(
pLevelNumbers.LevelNumbersArray[indentationLevel] + 1);
else
levelNumbers.Add(pLevelNumbers
.LevelNumbersArray[indentationLevel]);
continue;
}
}
if (level == indentationLevel)
{
int c1 = GetLevelNumberForLevel(numbering, styles, paragraph,
indentationLevel);
int? start2 = listItemInfo.Start;
if (start2 == null)
start2 = 1;
levelNumbers.Add(c1 + (int)start2);
continue;
}
levelNumbers.Add((int)start);
}
return levelNumbers.ToArray();
}
private static IEnumerable<string> GetFormatTokens(string lvlText)
{
int i = 0;
while (true)
{
if (i >= lvlText.Length)
yield break;
if (lvlText[i] == '%' && i <= lvlText.Length - 2)
{
yield return lvlText.Substring(i, 2);
i += 2;
continue;
}
int percentIndex = lvlText.IndexOf('%', i);
if (percentIndex == -1 || percentIndex > lvlText.Length - 2)
{
yield return lvlText.Substring(i);
yield break;
}
yield return lvlText.Substring(i, percentIndex - i);
yield return lvlText.Substring(percentIndex, 2);
i = percentIndex + 2;
}
}
private static string[] RomanOnes =
{
"", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX"
};
private static string[] RomanTens =
{
"", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC"
};
private static string[] RomanHundreds =
{
"", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM", "M"
};
private static string[] RomanThousands =
{
"", "M", "MM", "MMM", "MMMM", "MMMMM", "MMMMMM", "MMMMMMM", "MMMMMMMM",
"MMMMMMMMM", "MMMMMMMMMM"
};
private static string[] OneThroughNineteen = {
"one", "two", "three", "four", "five", "six", "seven", "eight",
"nine", "ten", "eleven", "twelve", "thirteen", "fourteen",
"fifteen", "sixteen", "seventeen", "eighteen", "nineteen"
};
private static string[] Tens = {
"ten", "twenty", "thirty", "forty", "fifty", "sixty", "seventy",
"eighty", "ninety"
};
private static string[] OrdinalOneThroughNineteen = {
"first", "second", "third", "fourth", "fifth", "sixth",
"seventh", "eighth", "ninth", "tenth", "eleventh", "twelfth",
"thirteenth", "fourteenth", "fifteenth", "sixteenth",
"seventeenth", "eighteenth", "nineteenth"
};
private static string[] OrdinalTenths = {
"tenth", "twentieth", "thirtieth", "fortieth", "fiftieth",
"sixtieth", "seventieth", "eightieth", "ninetieth"
};
private static string GetLevelText(int levelNumber, string numFmt)
{
if (numFmt == "decimal")
{
return levelNumber.ToString();
}
if (numFmt == "decimalZero")
{
if (levelNumber <= 9)
return "0" + levelNumber.ToString();
else
return levelNumber.ToString();
}
if (numFmt == "upperRoman")
{
int ones = levelNumber % 10;
int tens = (levelNumber % 100) / 10;
int hundreds = (levelNumber % 1000) / 100;
int thousands = levelNumber / 1000;
return RomanThousands[thousands] + RomanHundreds[hundreds] +
RomanTens[tens] + RomanOnes[ones];
}
if (numFmt == "lowerRoman")
{
int ones = levelNumber % 10;
int tens = (levelNumber % 100) / 10;
int hundreds = (levelNumber % 1000) / 100;
int thousands = levelNumber / 1000;
return (RomanThousands[thousands] + RomanHundreds[hundreds] +
RomanTens[tens] + RomanOnes[ones]).ToLower();
}
if (numFmt == "upperLetter")
{
string a = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
int c = (levelNumber - 1) / 26;
int n = (levelNumber - 1) % 26;
char x = a[n];
return "".PadRight(c + 1, x);
}
if (numFmt == "lowerLetter")
{
string a = "abcdefghijklmnopqrstuvwxyz";
int c = (levelNumber - 1) / 26;
int n = (levelNumber - 1) % 26;
char x = a[n];
return "".PadRight(c + 1, x);
}
if (numFmt == "ordinal")
{
string suffix;
if (levelNumber % 100 == 11 || levelNumber % 100 == 12 ||
levelNumber % 100 == 13)
suffix = "th";
else if (levelNumber % 10 == 1)
suffix = "st";
else if (levelNumber % 10 == 2)
suffix = "nd";
else if (levelNumber % 10 == 3)
suffix = "rd";
else
suffix = "th";
return levelNumber.ToString() + suffix;
}
if (numFmt == "cardinalText")
{
string result = "";
int t1 = levelNumber / 1000;
int t2 = levelNumber % 1000;
if (t1 >= 1)
result += OneThroughNineteen[t1 - 1] + " thousand";
if (t1 >= 1 && t2 == 0)
return result.Substring(0, 1).ToUpper() +
result.Substring(1);
if (t1 >= 1)
result += " ";
int h1 = (levelNumber % 1000) / 100;
int h2 = levelNumber % 100;
if (h1 >= 1)
result += OneThroughNineteen[h1 - 1] + " hundred";
if (h1 >= 1 && h2 == 0)
return result.Substring(0, 1).ToUpper() +
result.Substring(1);
if (h1 >= 1)
result += " ";
int z = levelNumber % 100;
if (z <= 19)
result += OneThroughNineteen[z - 1];
else
{
int x = z / 10;
int r = z % 10;
result += Tens[x - 1];
if (r >= 1)
result += "-" + OneThroughNineteen[r - 1];
}
return result.Substring(0, 1).ToUpper() +
result.Substring(1);
}
if (numFmt == "ordinalText")
{
string result = "";
int t1 = levelNumber / 1000;
int t2 = levelNumber % 1000;
if (t1 >= 1 && t2 != 0)
result += OneThroughNineteen[t1 - 1] + " thousand";
if (t1 >= 1 && t2 == 0)
{
result += OneThroughNineteen[t1 - 1] + " thousandth";
return result.Substring(0, 1).ToUpper() +
result.Substring(1);
}
if (t1 >= 1)
result += " ";
int h1 = (levelNumber % 1000) / 100;
int h2 = levelNumber % 100;
if (h1 >= 1 && h2 != 0)
result += OneThroughNineteen[h1 - 1] + " hundred";
if (h1 >= 1 && h2 == 0)
{
result += OneThroughNineteen[h1 - 1] + " hundredth";
return result.Substring(0, 1).ToUpper() +
result.Substring(1);
}
if (h1 >= 1)
result += " ";
int z = levelNumber % 100;
if (z <= 19)
result += OrdinalOneThroughNineteen[z - 1];
else
{
int x = z / 10;
int r = z % 10;
if (r == 0)
result += OrdinalTenths[x - 1];
else
result += Tens[x - 1];
if (r >= 1)
result += "-" + OrdinalOneThroughNineteen[r - 1];
}
return result.Substring(0, 1).ToUpper() +
result.Substring(1);
}
if (numFmt == "bullet")
return "";
// This method needs to be enhanced to support all languages and
// all number formats.
return levelNumber.ToString();
}
private static XElement GetRelatedLevel(XElement lvl, int level)
{
XElement parent = lvl.Parent;
XElement newLvl;
if (parent.Name == W.lvlOverride)
{
newLvl = parent.Parent.Elements(W.lvlOverride).Elements(W.lvl)
.Where(e => (int)e.Attribute(W.ilvl) == level).FirstOrDefault();
if (newLvl != null)
return newLvl;
int abstractNumId = (int)parent.Parent.Elements(W.abstractNumId)
.Attributes(W.val).FirstOrDefault();
XElement abstractNum = lvl.Ancestors().Last().Elements(W.abstractNum)
.Where(e => (int)e.Attribute(W.abstractNumId) == abstractNumId)
.FirstOrDefault();
newLvl = abstractNum.Elements(W.lvl)
.Where(e => (int)e.Attribute(W.ilvl) == level).FirstOrDefault();
return newLvl;
}
newLvl = parent.Elements(W.lvl).Where(e => (int)e.Attribute(W.ilvl) == level)
.FirstOrDefault();
return newLvl;
}
private static string FormatListItem(XElement lvl, int[] levelNumbers,
string lvlText, string bulletReplacementString)
{
if (bulletReplacementString != null)
{
StringBuilder sb = new StringBuilder(lvlText);
sb.Replace("\xF076", bulletReplacementString);
sb.Replace("\xF0D8", bulletReplacementString);
sb.Replace("\xF0A7", bulletReplacementString);
sb.Replace("\xF0B7", bulletReplacementString);
sb.Replace("\xF0A8", bulletReplacementString);
sb.Replace("\xF0FC", bulletReplacementString);
lvlText = sb.ToString();
}
string[] formatTokens = GetFormatTokens(lvlText).ToArray();
bool isLgl = lvl.Elements(W.isLgl).Any();
string listItem = formatTokens.Select((t, l) =>
{
if (t.Substring(0, 1) != "%")
return t;
int indentationLevel = Int32.Parse(t.Substring(1)) - 1;
int levelNumber = levelNumbers[indentationLevel];
string levelText;
XElement rlvl = GetRelatedLevel(lvl, indentationLevel);
string numFmtForLevel = (string)rlvl.Elements(W.numFmt).Attributes(W.val)
.FirstOrDefault();
if (isLgl && numFmtForLevel != "decimalZero")
numFmtForLevel = "decimal";
levelText = GetLevelText(levelNumber, numFmtForLevel);
return levelText;
}).StringConcatenate();
return listItem + " ";
}
public static string RetrieveListItem(WmlDocument document,
XElement paragraph, string bulletReplacementString)
{
using (OpenXmlMemoryStreamDocument streamDoc = new OpenXmlMemoryStreamDocument(document))
{
using (WordprocessingDocument wdoc = streamDoc.GetWordprocessingDocument())
{
return RetrieveListItem(wdoc, paragraph, bulletReplacementString);
}
}
}
public static string RetrieveListItem(WordprocessingDocument wordDoc,
XElement paragraph, string bulletReplacementString)
{
string pt = paragraph.Elements(W.r).Elements(W.t).Select(e => e.Value)
.StringConcatenate();
NumberingDefinitionsPart numberingDefinitionsPart =
wordDoc.MainDocumentPart.NumberingDefinitionsPart;
if (numberingDefinitionsPart == null)
return null;
StyleDefinitionsPart styleDefinitionsPart = wordDoc.MainDocumentPart
.StyleDefinitionsPart;
if (styleDefinitionsPart == null)
return null;
XDocument numbering = numberingDefinitionsPart.GetXDocument();
XDocument styles = styleDefinitionsPart.GetXDocument();
ListItemInfo listItemInfo = GetListItemInfo(numbering, styles, paragraph);
if (listItemInfo.IsListItem)
{
string lvlText = (string)listItemInfo.Lvl.Elements(W.lvlText)
.Attributes(W.val).FirstOrDefault();
int[] levelNumbers = GetLevelNumbers(numbering, styles, paragraph);
paragraph.AddAnnotation(new LevelNumbers()
{
LevelNumbersArray = levelNumbers
});
string listItem = FormatListItem(listItemInfo.Lvl, levelNumbers, lvlText,
bulletReplacementString);
return listItem;
}
return null;
}
}
}