Ver código fonte

feat (ReplaceHandler) Allow a replace to take a func to do work against a regex match. Useful for mailmerge scenarios.

master
jfinch 10 anos atrás
pai
commit
cb88e3b242

+ 239
- 0
.gitignore Ver arquivo

@@ -0,0 +1,239 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
# User-specific files
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
# Visual Studio 2015 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUNIT
*.VisualState.xml
TestResult.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# DNX
project.lock.json
artifacts/
*_i.c
*_p.c
*_i.h
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding add-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# TODO: Comment the next line if you want to checkin your web deploy settings
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# NuGet Packages
*.nupkg
# The packages folder can be ignored because of Package Restore
**/packages/*
# except build/, which is used as an MSBuild target.
!**/packages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/packages/repositories.config
# NuGet v3's project.json files produces more ignoreable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Microsoft Azure ApplicationInsights config file
ApplicationInsights.config
# Windows Store app package directory
AppPackages/
BundleArtifacts/
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.pfx
*.publishsettings
node_modules/
orleans.codegen.cs
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
# SQL Server files
*.mdf
*.ldf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
# FAKE - F# Make
.fake/
Documentation/Help/Documentation.chw
DocX/bin/Debug/DocX.dll

+ 47
- 20
DocX/Container.cs Ver arquivo

@@ -422,32 +422,62 @@ namespace Novacode
return uniqueResults.Keys.ToList(); // return the unique list of results
}
public virtual void ReplaceText(string oldValue, string newValue, bool trackChanges = false, RegexOptions options = RegexOptions.None, Formatting newFormatting = null, Formatting matchFormatting = null, MatchFormattingOptions fo = MatchFormattingOptions.SubsetMatch)
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)
{
if (oldValue == null || oldValue.Length == 0)
throw new ArgumentException("oldValue cannot be null or empty", "oldValue");
if (string.IsNullOrEmpty(searchValue))
throw new ArgumentException("oldValue cannot be null or empty", "searchValue");
if (newValue == null)
throw new ArgumentException("newValue cannot be null or empty", "newValue");
// ReplaceText in Headers of the document.
Headers headers = Document.Headers;
List<Header> headerList = new List<Header> { headers.first, headers.even, headers.odd };
foreach (Header h in headerList)
if (h != null)
foreach (Paragraph p in h.Paragraphs)
p.ReplaceText(oldValue, newValue, trackChanges, options, newFormatting, matchFormatting, fo);
var headerList = new List<Header> { Document.Headers.first, Document.Headers.even, Document.Headers.odd };
foreach (var header in headerList)
if (header != null)
foreach (var paragraph in header.Paragraphs)
paragraph.ReplaceText(searchValue, newValue, trackChanges, options, newFormatting, matchFormatting, formattingOptions);
// ReplaceText int main body of document.
foreach (Paragraph p in Paragraphs)
p.ReplaceText(oldValue, newValue, trackChanges, options, newFormatting, matchFormatting, fo);
foreach (var paragraph in Paragraphs)
paragraph.ReplaceText(searchValue, newValue, trackChanges, options, newFormatting, matchFormatting, formattingOptions);
// ReplaceText in Footers of the document.
Footers footers = Document.Footers;
List<Footer> footerList = new List<Footer> { footers.first, footers.even, footers.odd };
foreach (Footer f in footerList)
if (f != null)
foreach (Paragraph p in f.Paragraphs)
p.ReplaceText(oldValue, newValue, trackChanges, options, newFormatting, matchFormatting, fo);
var footerList = new List<Footer> { Document.Footers.first, Document.Footers.even, Document.Footers.odd };
foreach (var footer in footerList)
if (footer != null)
foreach (var paragraph in footer.Paragraphs)
paragraph.ReplaceText(searchValue, newValue, trackChanges, options, newFormatting, matchFormatting, formattingOptions);
}
/// <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)
{
if (string.IsNullOrEmpty(searchValue))
throw new ArgumentException("oldValue cannot be null or empty", "searchValue");
if (regexMatchHandler == null)
throw new ArgumentException("regexMatchHandler cannot be null", "regexMatchHandler");
// ReplaceText in Headers/Footers of the document.
var containerList = new List<IParagraphContainer> {
Document.Headers.first, Document.Headers.even, Document.Headers.odd,
Document.Footers.first, Document.Footers.even, Document.Footers.odd };
foreach (var container in containerList)
if (container != null)
foreach (var paragraph in container.Paragraphs)
paragraph.ReplaceText(searchValue, regexMatchHandler, trackChanges, options, newFormatting, matchFormatting, formattingOptions);
// ReplaceText int main body of document.
foreach (var paragraph in Paragraphs)
paragraph.ReplaceText(searchValue, regexMatchHandler, trackChanges, options, newFormatting, matchFormatting, formattingOptions);
}
/// <summary>
@@ -742,7 +772,6 @@ namespace Novacode
public virtual void InsertSection()
{
InsertSection(false);
}
@@ -916,8 +945,6 @@ namespace Novacode
{
foreach (var item in list.Items)
{
// item.Font(System.Drawing.FontFamily fontFamily)
Xml.Add(item.Xml);
}

+ 1
- 0
DocX/DocX.csproj Ver arquivo

@@ -112,6 +112,7 @@
<Compile Include="Headers.cs" />
<Compile Include="HelperFunctions.cs" />
<Compile Include="Hyperlink.cs" />
<Compile Include="IParagraphContainer.cs" />
<Compile Include="List.cs" />
<Compile Include="PageLayout.cs" />
<Compile Include="Section.cs" />

+ 1
- 1
DocX/Footer.cs Ver arquivo

@@ -6,7 +6,7 @@ using System.Collections.ObjectModel;
namespace Novacode
{
public class Footer : Container
public class Footer : Container, IParagraphContainer
{
public bool PageNumbers
{

+ 1
- 1
DocX/Header.cs Ver arquivo

@@ -7,7 +7,7 @@ using System.Collections.ObjectModel;
namespace Novacode
{
public class Header : Container
public class Header : Container, IParagraphContainer
{
public bool PageNumbers
{

+ 9
- 0
DocX/IParagraphContainer.cs Ver arquivo

@@ -0,0 +1,9 @@
using System.Collections.ObjectModel;
namespace Novacode
{
public interface IParagraphContainer
{
ReadOnlyCollection<Paragraph> Paragraphs { get; }
}
}

+ 65
- 1
DocX/Paragraph.cs Ver arquivo

@@ -3803,7 +3803,7 @@ namespace Novacode
/// <param name="fo">How should formatting be matched?</param>
public void ReplaceText(string oldValue, string newValue, bool trackChanges = false, RegexOptions options = RegexOptions.None, Formatting newFormatting = null, Formatting matchFormatting = null, MatchFormattingOptions fo = MatchFormattingOptions.SubsetMatch)
{
MatchCollection mc = Regex.Matches(this.Text, Regex.Escape(oldValue), options);
MatchCollection mc = Regex.Matches(Text, Regex.Escape(oldValue), options);
// Loop through the matches in reverse order
foreach (Match m in mc.Cast<Match>().Reverse())
@@ -3853,6 +3853,70 @@ namespace Novacode
}
}
/// <summary>
/// Find pattern regex must return a group match.
/// </summary>
/// <param name="findPattern">Regex pattern that must include one group match. ie (.*)</param>
/// <param name="regexMatchHandler">A func that accepts the matching find grouping text and returns a replacement value</param>
/// <param name="trackChanges"></param>
/// <param name="options"></param>
/// <param name="newFormatting"></param>
/// <param name="matchFormatting"></param>
/// <param name="fo"></param>
public void ReplaceText(string findPattern, Func<string,string> regexMatchHandler, bool trackChanges = false, RegexOptions options = RegexOptions.None, Formatting newFormatting = null, Formatting matchFormatting = null, MatchFormattingOptions fo = MatchFormattingOptions.SubsetMatch)
{
var matchCollection = Regex.Matches(Text, findPattern, options);
// Loop through the matches in reverse order
foreach (var match in matchCollection.Cast<Match>().Reverse())
{
// Assume the formatting matches until proven otherwise.
bool formattingMatch = true;
// Does the user want to match formatting?
if (matchFormatting != null)
{
// The number of characters processed so far
int processed = 0;
do
{
// Get the next run effected
Run run = GetFirstRunEffectedByEdit(match.Index + processed);
// Get this runs properties
XElement rPr = run.Xml.Element(XName.Get("rPr", DocX.w.NamespaceName));
if (rPr == null)
rPr = new Formatting().Xml;
/*
* Make sure that every formatting element in f.xml is also in this run,
* if this is not true, then their formatting does not match.
*/
if (!HelperFunctions.ContainsEveryChildOf(matchFormatting.Xml, rPr, fo))
{
formattingMatch = false;
break;
}
// We have processed some characters, so update the counter.
processed += run.Value.Length;
} while (processed < match.Length);
}
// If the formatting matches, do the replace.
if (formattingMatch)
{
var newValue = regexMatchHandler.Invoke(match.Groups[1].Value);
InsertText(match.Index + match.Value.Length, newValue, trackChanges, newFormatting);
RemoveText(match.Index, match.Value.Length, trackChanges);
}
}
}
/// <summary>
/// Find all instances of a string in this paragraph and return their indexes in a List.
/// </summary>

+ 34
- 1
UnitTests/DocXUnitTests.cs Ver arquivo

@@ -139,6 +139,36 @@ namespace UnitTests
}
public void TestPatternFuncReplacement()
{
}
public string ReplaceFunc(string findStr)
{
Dictionary<string, string> testPatterns = new Dictionary<string, string>()
{
{"COURT NAME","Fred Frump"},
{"Case Number","cr-md-2011-1234567"}
};
if (testPatterns.ContainsKey(findStr))
{
return testPatterns[findStr];
}
return findStr;
}
[TestMethod]
public void RegexTest()
{
var findPattern = "<(.*?)>";
var sample = "<Match This> text";
var matchCollection = Regex.Matches(sample, findPattern,RegexOptions.IgnoreCase);
int i = 1;
}
[TestMethod]
public void Test_Pattern_Replacement()
{
@@ -171,7 +201,10 @@ namespace UnitTests
// Do the replacing
foreach (var p in testPatterns)
replaceDoc.ReplaceText("<" + p.Key + ">", p.Value, false, RegexOptions.IgnoreCase);
{
replaceDoc.ReplaceText("<(.*?)>", ReplaceFunc,false,RegexOptions.IgnoreCase);
//replaceDoc.ReplaceText("<" + p.Key + ">", p.Value, false, RegexOptions.IgnoreCase);
}
// Make sure the origional string are no longer in the document.
Assert.IsTrue(replaceDoc.FindAll("<COURT NAME>").Count == 0);

+ 74
- 0
UnitTests/ReplaceTests.cs Ver arquivo

@@ -0,0 +1,74 @@
using System.Collections.Generic;
using System.Text.RegularExpressions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Novacode;
namespace UnitTests
{
[TestClass]
public class RegExTest
{
private readonly Dictionary<string, string> _testPatterns = new Dictionary<string, string>
{
{ "COURT NAME", "Fred Frump" },
{ "Case Number", "cr-md-2011-1234567" }
};
private readonly TestHelper _testHelper;
public RegExTest()
{
_testHelper = new TestHelper();
}
[TestMethod]
public void ReplaceText_Can_ReplaceViaFunctionHandler()
{
using (var replaceDoc = DocX.Load(_testHelper.DirectoryWithFiles + "ReplaceTests.docx"))
{
foreach (var t in replaceDoc.Tables)
{
// each table has 1 row and 3 columns
Assert.IsTrue(t.Rows[0].Cells.Count == 3);
Assert.IsTrue(t.ColumnCount == 3);
Assert.IsTrue(t.Rows.Count == 1);
Assert.IsTrue(t.RowCount == 1);
}
// Make sure the origional strings are in the document.
Assert.IsTrue(replaceDoc.FindAll("<COURT NAME>").Count == 2);
Assert.IsTrue(replaceDoc.FindAll("<Case Number>").Count == 2);
// There are only two patterns, even though each pattern is used more than once
Assert.IsTrue(replaceDoc.FindUniqueByPattern(@"<[\w \=]{4,}>", RegexOptions.IgnoreCase).Count == 2);
// Make sure the new strings are not in the document.
Assert.IsTrue(replaceDoc.FindAll("Fred Frump").Count == 0);
Assert.IsTrue(replaceDoc.FindAll("cr-md-2011-1234567").Count == 0);
// Do the replacing
replaceDoc.ReplaceText("<(.*?)>", ReplaceTextHandler, false, RegexOptions.IgnoreCase);
// Make sure the origional string are no longer in the document.
Assert.IsTrue(replaceDoc.FindAll("<COURT NAME>").Count == 0);
Assert.IsTrue(replaceDoc.FindAll("<Case Number>").Count == 0);
// Make sure the new strings are now in the document.
Assert.IsTrue(replaceDoc.FindAll("FRED FRUMP").Count == 2);
Assert.IsTrue(replaceDoc.FindAll("cr-md-2011-1234567").Count == 2);
// Make sure the replacement worked.
Assert.IsTrue(replaceDoc.Text
== "\t\t\t\t\t\t\t\t\t\t\t\t\t\tThese two tables should look identical:\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tSTATE OF IOWA,\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tPlaintiff,\t\t\t\t\t\t\t\t\t\t\t\t\t\tvs.\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tFRED FRUMP,\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tDefendant.\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tCase No.: cr-md-2011-1234567\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tORDER SETTING ASIDE DEFAULT JUDGMENT\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tSTATE OF IOWA,\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tPlaintiff,\t\t\t\t\t\t\t\t\t\t\t\t\t\tvs.\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tFRED FRUMP,\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tDefendant.\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tCase No.: cr-md-2011-1234567\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tORDER SETTING ASIDE DEFAULT JUDGMENT\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t");
}
}
private string ReplaceTextHandler(string findStr)
{
if (_testPatterns.ContainsKey(findStr))
{
return _testPatterns[findStr];
}
return findStr;
}
}
}

+ 14
- 0
UnitTests/TestHelper.cs Ver arquivo

@@ -0,0 +1,14 @@
namespace UnitTests
{
public class TestHelper
{
public string DirectoryWithFiles { get; }
public TestHelper()
{
var relativeDirectory = new RelativeDirectory(); // prepares the files for testing
relativeDirectory.Up(3);
DirectoryWithFiles = relativeDirectory.Path + @"\UnitTests\documents\";
}
}
}

+ 2
- 0
UnitTests/UnitTests.csproj Ver arquivo

@@ -71,7 +71,9 @@
</Compile>
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="DocXUnitTests.cs" />
<Compile Include="RegExTest.cs" />
<Compile Include="RelativeDirectory.cs" />
<Compile Include="TestHelper.cs" />
</ItemGroup>
<ItemGroup>
<None Include="documents\EverybodyHasAHome.docx" />

Carregando…
Cancelar
Salvar