Przeglądaj źródła

Merge pull request #99 from VictorLoktev/master

Bugfix in Row.Height, removing empty paragraphs in cells, Row.InsertRow keeps formatting
master
PrzemyslawKlys 9 lat temu
rodzic
commit
b536b5e517

+ 18
- 17
DocX/Container.cs Wyświetl plik

return uniqueResults.Keys.ToList(); // return the unique list of results return uniqueResults.Keys.ToList(); // return the unique list of results
} }
public virtual void ReplaceText(string searchValue, string newValue, bool trackChanges = false, RegexOptions options = RegexOptions.None, Formatting newFormatting = null, Formatting matchFormatting = null, MatchFormattingOptions formattingOptions = MatchFormattingOptions.SubsetMatch, bool escapeRegEx = true, bool useRegExSubstitutions = false)
public virtual void ReplaceText(string searchValue, string newValue, bool trackChanges = false, RegexOptions options = RegexOptions.None, Formatting newFormatting = null, Formatting matchFormatting = null, MatchFormattingOptions formattingOptions = MatchFormattingOptions.SubsetMatch, bool escapeRegEx = true, bool useRegExSubstitutions = false, bool removeEmptyParagraph = true)
{ {
if (string.IsNullOrEmpty(searchValue)) if (string.IsNullOrEmpty(searchValue))
throw new ArgumentException("oldValue cannot be null or empty", "searchValue"); throw new ArgumentException("oldValue cannot be null or empty", "searchValue");
foreach (var header in headerList) foreach (var header in headerList)
if (header != null) if (header != null)
foreach (var paragraph in header.Paragraphs) foreach (var paragraph in header.Paragraphs)
paragraph.ReplaceText(searchValue, newValue, trackChanges, options, newFormatting, matchFormatting, formattingOptions, escapeRegEx, useRegExSubstitutions);
paragraph.ReplaceText(searchValue, newValue, trackChanges, options, newFormatting, matchFormatting, formattingOptions, escapeRegEx, useRegExSubstitutions, removeEmptyParagraph);
// ReplaceText int main body of document. // ReplaceText int main body of document.
foreach (var paragraph in Paragraphs) foreach (var paragraph in Paragraphs)
paragraph.ReplaceText(searchValue, newValue, trackChanges, options, newFormatting, matchFormatting, formattingOptions, escapeRegEx, useRegExSubstitutions);
paragraph.ReplaceText(searchValue, newValue, trackChanges, options, newFormatting, matchFormatting, formattingOptions, escapeRegEx, useRegExSubstitutions, removeEmptyParagraph);
// ReplaceText in Footers of the document. // ReplaceText in Footers of the document.
var footerList = new List<Footer> { Document.Footers.first, Document.Footers.even, Document.Footers.odd }; var footerList = new List<Footer> { Document.Footers.first, Document.Footers.even, Document.Footers.odd };
foreach (var footer in footerList) foreach (var footer in footerList)
if (footer != null) if (footer != null)
foreach (var paragraph in footer.Paragraphs) foreach (var paragraph in footer.Paragraphs)
paragraph.ReplaceText(searchValue, newValue, trackChanges, options, newFormatting, matchFormatting, formattingOptions, escapeRegEx, useRegExSubstitutions);
paragraph.ReplaceText(searchValue, newValue, trackChanges, options, newFormatting, matchFormatting, formattingOptions, escapeRegEx, useRegExSubstitutions, removeEmptyParagraph);
} }
/// <summary>
///
/// </summary>
/// <param name="searchValue">Value to find</param>
/// <param name="regexMatchHandler">A Func that accepts the matching regex search group value and passes it to this to return the replacement string</param>
/// <param name="trackChanges">Enable trackchanges</param>
/// <param name="options">Regex options</param>
/// <param name="newFormatting"></param>
/// <param name="matchFormatting"></param>
/// <param name="formattingOptions"></param>
public virtual void ReplaceText(string searchValue, Func<string, string> regexMatchHandler, bool trackChanges = false, RegexOptions options = RegexOptions.None, Formatting newFormatting = null, Formatting matchFormatting = null, MatchFormattingOptions formattingOptions = MatchFormattingOptions.SubsetMatch)
/// <summary>
///
/// </summary>
/// <param name="searchValue">Value to find</param>
/// <param name="regexMatchHandler">A Func that accepts the matching regex search group value and passes it to this to return the replacement string</param>
/// <param name="trackChanges">Enable trackchanges</param>
/// <param name="options">Regex options</param>
/// <param name="newFormatting"></param>
/// <param name="matchFormatting"></param>
/// <param name="formattingOptions"></param>
/// <param name="removeEmptyParagraph">Remove empty paragraph</param>
public virtual void ReplaceText(string searchValue, Func<string, string> regexMatchHandler, bool trackChanges = false, RegexOptions options = RegexOptions.None, Formatting newFormatting = null, Formatting matchFormatting = null, MatchFormattingOptions formattingOptions = MatchFormattingOptions.SubsetMatch, bool removeEmptyParagraph = true)
{ {
if (string.IsNullOrEmpty(searchValue)) if (string.IsNullOrEmpty(searchValue))
throw new ArgumentException("oldValue cannot be null or empty", "searchValue"); throw new ArgumentException("oldValue cannot be null or empty", "searchValue");
foreach (var container in containerList) foreach (var container in containerList)
if (container != null) if (container != null)
foreach (var paragraph in container.Paragraphs) foreach (var paragraph in container.Paragraphs)
paragraph.ReplaceText(searchValue, regexMatchHandler, trackChanges, options, newFormatting, matchFormatting, formattingOptions);
paragraph.ReplaceText(searchValue, regexMatchHandler, trackChanges, options, newFormatting, matchFormatting, formattingOptions, removeEmptyParagraph);
// ReplaceText int main body of document. // ReplaceText int main body of document.
foreach (var paragraph in Paragraphs) foreach (var paragraph in Paragraphs)
paragraph.ReplaceText(searchValue, regexMatchHandler, trackChanges, options, newFormatting, matchFormatting, formattingOptions);
paragraph.ReplaceText(searchValue, regexMatchHandler, trackChanges, options, newFormatting, matchFormatting, formattingOptions, removeEmptyParagraph);
} }
/// <summary> /// <summary>

+ 15
- 12
DocX/Paragraph.cs Wyświetl plik

/// <param name="count">The number of characters to delete</param> /// <param name="count">The number of characters to delete</param>
/// <param name="trackChanges">Track changes</param> /// <param name="trackChanges">Track changes</param>
/// <param name="removeEmptyParagraph">Remove empty paragraph</param> /// <param name="removeEmptyParagraph">Remove empty paragraph</param>
public void RemoveText(int index, int count, bool trackChanges = false, bool removeEmptyParagraph=true)
public void RemoveText(int index, int count, bool trackChanges = false, bool removeEmptyParagraph = true)
{ {
// Timestamp to mark the start of insert // Timestamp to mark the start of insert
DateTime now = DateTime.Now; DateTime now = DateTime.Now;
} }
} }
// If after this remove the parent element is empty, remove it.
if (removeEmptyParagraph && GetElementTextLength(parentElement) == 0)
{
if (parentElement.Parent != null && parentElement.Parent.Name.LocalName != "tc")
{
// Need to make sure there is no drawing element within the parent element.
// Picture elements contain no text length but they are still content.
if (parentElement.Descendants(XName.Get("drawing", DocX.w.NamespaceName)).Count() == 0)
parentElement.Remove();
}
}
// Removing of empty paragraph is allowed if text is empty and removeEmptyParagraph=true
bool removeEmpty = removeEmptyParagraph && GetElementTextLength( parentElement ) == 0;
if( parentElement.Parent != null )
{
// Need to make sure there is another paragraph in parent cell
removeEmpty &= parentElement.Parent.Name.LocalName == "tc" &&
parentElement.Parent.Elements( XName.Get( "p", DocX.w.NamespaceName ) ).Count() > 1;
// Need to make sure there is no drawing element within the parent element.
// Picture elements contain no text length but they are still content.
removeEmpty &= parentElement.Descendants( XName.Get( "drawing", DocX.w.NamespaceName ) ).Count() == 0;
}
if( removeEmpty )
parentElement.Remove();
} }
while (processed < count); while (processed < count);

+ 39
- 21
DocX/Table.cs Wyświetl plik

return InsertRow(RowCount); return InsertRow(RowCount);
} }
/// <summary>
/// Insert a copy of a row at the end of this table.
/// </summary>
/// <returns>A new row.</returns>
public Row InsertRow(Row row)
/// <summary>
/// Insert a copy of a row at the end of this table.
/// </summary>
/// <returns>A new row.</returns>
/// <param name="row">The row to insert</param>
/// <param name="keepFormatting">True to clone everithing, False to clone cell structure only.</param>
/// <returns></returns>
public Row InsertRow(Row row, bool keepFormatting = false)
{ {
return InsertRow(row, RowCount);
return InsertRow(row, RowCount, keepFormatting);
} }
/// <summary> /// <summary>
return InsertRow(content, index); return InsertRow(content, index);
} }
/// <summary>
/// Insert a copy of a row into this table.
/// </summary>
/// <param name="row">Row to copy and insert.</param>
/// <param name="index">Index to insert row at.</param>
/// <returns>A new Row</returns>
public Row InsertRow(Row row, int index)
/// <summary>
/// Insert a copy of a row into this table.
/// </summary>
/// <param name="row">Row to copy and insert.</param>
/// <param name="index">Index to insert row at.</param>
/// <param name="keepFormatting">True to clone everithing, False to clone cell structure only.</param>
/// <returns>A new Row</returns>
public Row InsertRow(Row row, int index, bool keepFormatting = false)
{ {
if (row == null) if (row == null)
throw new ArgumentNullException(nameof(row)); throw new ArgumentNullException(nameof(row));
if (index < 0 || index > RowCount) if (index < 0 || index > RowCount)
throw new IndexOutOfRangeException(); throw new IndexOutOfRangeException();
List<XElement> content = row.Xml.Elements(XName.Get("tc", DocX.w.NamespaceName)).Select(element => HelperFunctions.CloneElement(element)).ToList();
return InsertRow(content, index);
List<XElement> content;
if( keepFormatting )
content = row.Xml.Elements().Select(element => HelperFunctions.CloneElement(element)).ToList();
else
content = row.Xml.Elements(XName.Get("tc", DocX.w.NamespaceName)).Select(element => HelperFunctions.CloneElement(element)).ToList();
return InsertRow(content, index);
} }
private Row InsertRow(List<XElement> content, Int32 index) private Row InsertRow(List<XElement> content, Int32 index)
XElement trPr = Xml.Element(XName.Get("trPr", DocX.w.NamespaceName)); XElement trPr = Xml.Element(XName.Get("trPr", DocX.w.NamespaceName));
if (trPr == null) if (trPr == null)
{ {
Xml.SetElementValue(XName.Get("trPr", DocX.w.NamespaceName), string.Empty);
trPr = Xml.Element(XName.Get("trPr", DocX.w.NamespaceName));
}
Xml.SetElementValue(XName.Get("trPr", DocX.w.NamespaceName), string.Empty);
trPr = Xml.Element(XName.Get("trPr", DocX.w.NamespaceName));
/*
// Swapping trPr and tc elements - making trPr the first
XElement tc = Xml.Element( XName.Get( "tc", DocX.w.NamespaceName ) );
if( tc != null )
{
trPr.Remove();
tc.AddBeforeSelf( trPr );
}
}
/*
* Get the trHeight element for this Row, * Get the trHeight element for this Row,
* null will be return if no such element exists. * null will be return if no such element exists.
*/ */
XElement trHeight = trPr.Element(XName.Get("trHeight", DocX.w.NamespaceName));
XElement trHeight = trPr.Element(XName.Get("trHeight", DocX.w.NamespaceName));
if (trHeight == null) if (trHeight == null)
{ {
trPr.SetElementValue(XName.Get("trHeight", DocX.w.NamespaceName), string.Empty); trPr.SetElementValue(XName.Get("trHeight", DocX.w.NamespaceName), string.Empty);
trHeight.SetAttributeValue(XName.Get("hRule", DocX.w.NamespaceName), exact ? _hRule_Exact : _hRule_AtLeast); trHeight.SetAttributeValue(XName.Get("hRule", DocX.w.NamespaceName), exact ? _hRule_Exact : _hRule_AtLeast);
// 15 "word units" is equal to one pixel. // 15 "word units" is equal to one pixel.
trHeight.SetAttributeValue(XName.Get("val", DocX.w.NamespaceName), (height * 15).ToString());
trHeight.SetAttributeValue(XName.Get("val", DocX.w.NamespaceName),
((int)(Math.Round(height * 15,0))).ToString( CultureInfo.InvariantCulture )); // national separators anf fraction should be avoided
} }
/// <summary> /// <summary>
/// Min-Height in pixels. // Added by Nick Kusters. /// Min-Height in pixels. // Added by Nick Kusters.

+ 8
- 3
DocX/bin/Release/DocX.XML Wyświetl plik

<param name="options"></param> <param name="options"></param>
<returns></returns> <returns></returns>
</member> </member>
<member name="M:Novacode.Container.ReplaceText(System.String,System.Func{System.String,System.String},System.Boolean,System.Text.RegularExpressions.RegexOptions,Novacode.Formatting,Novacode.Formatting,Novacode.MatchFormattingOptions)">
<member name="M:Novacode.Container.ReplaceText(System.String,System.Func{System.String,System.String},System.Boolean,System.Text.RegularExpressions.RegexOptions,Novacode.Formatting,Novacode.Formatting,Novacode.MatchFormattingOptions,System.Boolean)">
<summary> <summary>
</summary> </summary>
<param name="newFormatting"></param> <param name="newFormatting"></param>
<param name="matchFormatting"></param> <param name="matchFormatting"></param>
<param name="formattingOptions"></param> <param name="formattingOptions"></param>
<param name="removeEmptyParagraph">Remove empty paragraph</param>
</member> </member>
<member name="M:Novacode.Container.RemoveTextInGivenFormat(Novacode.Formatting,Novacode.MatchFormattingOptions)"> <member name="M:Novacode.Container.RemoveTextInGivenFormat(Novacode.Formatting,Novacode.MatchFormattingOptions)">
<summary> <summary>
</example> </example>
<returns>A new row.</returns> <returns>A new row.</returns>
</member> </member>
<member name="M:Novacode.Table.InsertRow(Novacode.Row)">
<member name="M:Novacode.Table.InsertRow(Novacode.Row,System.Boolean)">
<summary> <summary>
Insert a copy of a row at the end of this table. Insert a copy of a row at the end of this table.
</summary> </summary>
<returns>A new row.</returns> <returns>A new row.</returns>
<param name="row">The row to insert</param>
<param name="keepFormatting">True to clone everithing, False to clone cell structure only.</param>
<returns></returns>
</member> </member>
<member name="M:Novacode.Table.InsertColumn"> <member name="M:Novacode.Table.InsertColumn">
<summary> <summary>
<param name="index">Index to insert row at.</param> <param name="index">Index to insert row at.</param>
<returns>A new Row</returns> <returns>A new Row</returns>
</member> </member>
<member name="M:Novacode.Table.InsertRow(Novacode.Row,System.Int32)">
<member name="M:Novacode.Table.InsertRow(Novacode.Row,System.Int32,System.Boolean)">
<summary> <summary>
Insert a copy of a row into this table. Insert a copy of a row into this table.
</summary> </summary>
<param name="row">Row to copy and insert.</param> <param name="row">Row to copy and insert.</param>
<param name="index">Index to insert row at.</param> <param name="index">Index to insert row at.</param>
<param name="keepFormatting">True to clone everithing, False to clone cell structure only.</param>
<returns>A new Row</returns> <returns>A new Row</returns>
</member> </member>
<member name="M:Novacode.Table.InsertColumn(System.Int32,System.Boolean)"> <member name="M:Novacode.Table.InsertColumn(System.Int32,System.Boolean)">

BIN
DocX/bin/Release/DocX.dll Wyświetl plik


+ 125
- 1
UnitTests/DocXUnitTests.cs Wyświetl plik

} }
} }
public string ReplaceFunc(string findStr)
/// <summary>
/// TextRemove should not remove empty paragraphs in case the paragraph is alone in the cell.
/// In the rest cases empty paragraph may be removed.
/// </summary>
[Test]
public void Test_Table_Paragraph_RemoveText()
{
using( var input = File.Open( Path.Combine( _directoryWithFiles, "TableSpecifiedHeights.docx" ), FileMode.Open ) )
{
using( var doc = DocX.Load( input ) )
{
// Make sure content of the file is ok for test
Assert.IsTrue( doc.Tables.Count == 1 );
Assert.IsTrue( doc.Tables[ 0 ].RowCount == 3 );
string text = "paragraph";
// == Paragraph in the cell is not alone ==
doc.Tables[ 0 ].Rows[ 0 ].Cells[ 0 ].InsertParagraph( text );
Assert.IsTrue( doc.Tables[ 0 ].Rows[ 0 ].Cells[ 0 ].Paragraphs.Count == 2 );
doc.Tables[ 0 ].Rows[ 0 ].Cells[ 0 ].ReplaceText( text, "", removeEmptyParagraph: true );
Assert.IsTrue( doc.Tables[ 0 ].Rows[ 0 ].Cells[ 0 ].Paragraphs.Count == 1 );
doc.Tables[ 0 ].Rows[ 0 ].Cells[ 0 ].InsertParagraph( text );
Assert.IsTrue( doc.Tables[ 0 ].Rows[ 0 ].Cells[ 0 ].Paragraphs.Count == 2 );
doc.Tables[ 0 ].Rows[ 0 ].Cells[ 0 ].ReplaceText( text, "", removeEmptyParagraph: false );
Assert.IsTrue( doc.Tables[ 0 ].Rows[ 0 ].Cells[ 0 ].Paragraphs.Count == 2 );
// == Paragraph in the cell is alone ==
doc.Tables[ 0 ].Rows[ 0 ].Cells[ 0 ].InsertParagraph( text );
doc.Tables[ 0 ].Rows[ 0 ].Cells[ 0 ].Paragraphs[ 0 ].Remove( false );
doc.Tables[ 0 ].Rows[ 0 ].Cells[ 0 ].Paragraphs[ 0 ].Remove( false );
Assert.IsTrue( doc.Tables[ 0 ].Rows[ 0 ].Cells[ 0 ].Paragraphs.Count == 1 );
doc.Tables[ 0 ].Rows[ 0 ].Cells[ 0 ].ReplaceText( text, "", removeEmptyParagraph: true );
Assert.IsTrue( doc.Tables[ 0 ].Rows[ 0 ].Cells[ 0 ].Paragraphs.Count == 1 );
doc.Tables[ 0 ].Rows[ 0 ].Cells[ 0 ].InsertParagraph( text );
Assert.IsTrue( doc.Tables[ 0 ].Rows[ 0 ].Cells[ 0 ].Paragraphs.Count == 2 );
doc.Tables[ 0 ].Rows[ 0 ].Cells[ 0 ].ReplaceText( text, "", removeEmptyParagraph: false );
Assert.IsTrue( doc.Tables[ 0 ].Rows[ 0 ].Cells[ 0 ].Paragraphs.Count == 2 );
}
}
}
[Test]
public void Test_Table_MinHeight()
{
using( var input = File.Open( Path.Combine( _directoryWithFiles, "TableSpecifiedHeights.docx" ), FileMode.Open ) )
{
using( var doc = DocX.Load( input ) )
{
// Make sure content of the file is ok for test
Assert.IsTrue( doc.Tables.Count == 1 );
Assert.IsTrue( doc.Tables[ 0 ].RowCount == 3 );
// Check heights load is ok
Assert.IsTrue( double.IsNaN( doc.Tables[ 0 ].Rows[ 0 ].Height ) );
Assert.IsTrue( double.IsNaN( doc.Tables[ 0 ].Rows[ 0 ].MinHeight ) );
Assert.IsTrue( Math.Abs( doc.Tables[ 0 ].Rows[ 1 ].Height - 37.8f ) < 0.0001f );
Assert.IsTrue( Math.Abs( doc.Tables[ 0 ].Rows[ 1 ].MinHeight - 37.8f ) < 0.0001f );
Assert.IsTrue( Math.Abs( doc.Tables[ 0 ].Rows[ 2 ].Height - 37.8f ) < 0.0001f );
Assert.IsTrue( Math.Abs( doc.Tables[ 0 ].Rows[ 2 ].MinHeight - 37.8f ) < 0.0001f );
// Set MinHeight
doc.Tables[ 0 ].Rows[ 0 ].MinHeight = 37.8f;
Assert.IsTrue( Math.Abs( doc.Tables[ 0 ].Rows[ 0 ].Height - 37.8f ) < 0.0001f );
Assert.IsTrue( Math.Abs( doc.Tables[ 0 ].Rows[ 0 ].MinHeight - 37.8f ) < 0.0001f );
}
}
}
[Test]
public void Test_Table_InsertRow_Keeps_Formatting()
{
using( var input = File.Open( Path.Combine( _directoryWithFiles, "TableSpecifiedHeights.docx" ), FileMode.Open ) )
{
using( var doc = DocX.Load( input ) )
{
// Make sure content of the file is ok for test
Assert.IsTrue( doc.Tables.Count == 1 );
Assert.IsTrue( doc.Tables[ 0 ].RowCount == 3 );
// Check heights load is ok
Assert.IsTrue( double.IsNaN( doc.Tables[ 0 ].Rows[ 0 ].Height ) );
Assert.IsTrue( double.IsNaN( doc.Tables[ 0 ].Rows[ 0 ].MinHeight ) );
Assert.IsTrue( Math.Abs( doc.Tables[ 0 ].Rows[ 1 ].Height - 37.8f ) < 0.0001f );
Assert.IsTrue( Math.Abs( doc.Tables[ 0 ].Rows[ 1 ].MinHeight - 37.8f ) < 0.0001f );
Assert.IsTrue( Math.Abs( doc.Tables[ 0 ].Rows[ 2 ].Height - 37.8f ) < 0.0001f );
Assert.IsTrue( Math.Abs( doc.Tables[ 0 ].Rows[ 2 ].MinHeight - 37.8f ) < 0.0001f );
// Clone all rows and check heights
int n = doc.Tables[ 0 ].RowCount;
for( int index = 0; index < n; index++ )
{
doc.Tables[ 0 ].InsertRow( doc.Tables[ 0 ].Rows[ index ], true );
}
Assert.IsTrue( doc.Tables[ 0 ].RowCount == 2 * n );
for( int index = 0; index < n; index++ )
{
// Compare height of original row and cloned
Assert.IsTrue( double.IsNaN( doc.Tables[ 0 ].Rows[ n + index ].Height ) == double.IsNaN( doc.Tables[ 0 ].Rows[ index ].Height ) );
if( !double.IsNaN( doc.Tables[ 0 ].Rows[ n + index ].Height ) && !double.IsNaN( doc.Tables[ 0 ].Rows[ index ].Height ) )
Assert.IsTrue( Math.Abs( doc.Tables[ 0 ].Rows[ n + index ].Height - doc.Tables[ 0 ].Rows[ index ].Height ) < 0.0001f );
Assert.IsTrue( double.IsNaN( doc.Tables[ 0 ].Rows[ n + index ].MinHeight ) == double.IsNaN( doc.Tables[ 0 ].Rows[ index ].MinHeight ) );
if( !double.IsNaN( doc.Tables[ 0 ].Rows[ n + index ].MinHeight ) && !double.IsNaN( doc.Tables[ 0 ].Rows[ index ].MinHeight ) )
Assert.IsTrue( Math.Abs( doc.Tables[ 0 ].Rows[ n + index ].MinHeight - doc.Tables[ 0 ].Rows[ index ].MinHeight ) < 0.0001f );
}
// Remove original rows
for( int index = 0; index < n; index++ )
{
doc.Tables[ 0 ].Rows[ 0 ].Remove();
}
// At this point we shuold have document visually equal to original
doc.SaveAs( Path.Combine( _directoryWithFiles, "TableSpecifiedHeights_out.docx" ) );
}
}
}
public string ReplaceFunc(string findStr)
{ {
var testPatterns = new Dictionary<string, string> var testPatterns = new Dictionary<string, string>
{ {

+ 3
- 0
UnitTests/UnitTests.csproj Wyświetl plik

<None Include="documents\Tables.docx"> <None Include="documents\Tables.docx">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None> </None>
<None Include="documents\TableSpecifiedHeights.docx">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="documents\TableSpecifiedWidths.docx"> <None Include="documents\TableSpecifiedWidths.docx">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None> </None>

BIN
UnitTests/documents/TableSpecifiedHeights.docx Wyświetl plik


Ładowanie…
Anuluj
Zapisz